diff --git a/src/mlpack/core/dists/CMakeLists.txt b/src/mlpack/core/dists/CMakeLists.txt index c8365faac3..5368470db7 100644 --- a/src/mlpack/core/dists/CMakeLists.txt +++ b/src/mlpack/core/dists/CMakeLists.txt @@ -2,17 +2,17 @@ # Anything not in this list will not be compiled into mlpack. set(SOURCES discrete_distribution.hpp - discrete_distribution.cpp + discrete_distribution_impl.hpp gaussian_distribution.hpp - gaussian_distribution.cpp + gaussian_distribution_impl.hpp laplace_distribution.hpp - laplace_distribution.cpp + laplace_distribution_impl.hpp regression_distribution.hpp - regression_distribution.cpp + regression_distribution_impl.hpp gamma_distribution.hpp - gamma_distribution.cpp + gamma_distribution_impl.hpp diagonal_gaussian_distribution.hpp - diagonal_gaussian_distribution.cpp + diagonal_gaussian_distribution_impl.hpp ) # add directory name to sources diff --git a/src/mlpack/core/dists/diagonal_gaussian_distribution.hpp b/src/mlpack/core/dists/diagonal_gaussian_distribution.hpp index 05c5e54e2c..76515cd3ec 100644 --- a/src/mlpack/core/dists/diagonal_gaussian_distribution.hpp +++ b/src/mlpack/core/dists/diagonal_gaussian_distribution.hpp @@ -15,7 +15,7 @@ #include namespace mlpack { -namespace distribution { +namespace distribution /** Probability distributions. */ { //! A single multivariate Gaussian distribution with diagonal covariance. class DiagonalGaussianDistribution @@ -153,4 +153,7 @@ class DiagonalGaussianDistribution } // namespace distribution } // namespace mlpack +// Include implementation. +#include "diagonal_gaussian_distribution_impl.hpp" + #endif diff --git a/src/mlpack/core/dists/diagonal_gaussian_distribution.cpp b/src/mlpack/core/dists/diagonal_gaussian_distribution_impl.hpp similarity index 76% rename from src/mlpack/core/dists/diagonal_gaussian_distribution.cpp rename to src/mlpack/core/dists/diagonal_gaussian_distribution_impl.hpp index bb58ea7931..817327330a 100644 --- a/src/mlpack/core/dists/diagonal_gaussian_distribution.cpp +++ b/src/mlpack/core/dists/diagonal_gaussian_distribution_impl.hpp @@ -1,5 +1,5 @@ /** - * @file core/dists/diagonal_gaussian_distribution.cpp + * @file core/dists/diagonal_gaussian_distribution_impl.hpp * @author Kim SangYeon * * Implementation of Gaussian distribution class with diagonal covariance. @@ -9,13 +9,16 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_CORE_DISTRIBUTIONS_DIAGONAL_GAUSSIAN_DISTRIBUTION_IMPL_HPP +#define MLPACK_CORE_DISTRIBUTIONS_DIAGONAL_GAUSSIAN_DISTRIBUTION_IMPL_HPP + #include "diagonal_gaussian_distribution.hpp" #include -using namespace mlpack; -using namespace mlpack::distribution; +namespace mlpack { +namespace distribution /** Probability distributions. */ { -DiagonalGaussianDistribution::DiagonalGaussianDistribution( +inline DiagonalGaussianDistribution::DiagonalGaussianDistribution( const arma::vec& mean, const arma::vec& covariance) : mean(mean) @@ -23,21 +26,21 @@ DiagonalGaussianDistribution::DiagonalGaussianDistribution( Covariance(covariance); } -void DiagonalGaussianDistribution::Covariance(const arma::vec& covariance) +inline void DiagonalGaussianDistribution::Covariance(const arma::vec& covariance) { - this->invCov = 1 / covariance; - this->logDetCov = arma::accu(log(covariance)); + invCov = 1 / covariance; + logDetCov = arma::accu(log(covariance)); this->covariance = covariance; } -void DiagonalGaussianDistribution::Covariance(arma::vec&& covariance) +inline void DiagonalGaussianDistribution::Covariance(arma::vec&& covariance) { - this->invCov = 1 / covariance; - this->logDetCov = arma::accu(log(covariance)); + invCov = 1 / covariance; + logDetCov = arma::accu(log(covariance)); this->covariance = std::move(covariance); } -double DiagonalGaussianDistribution::LogProbability( +inline double DiagonalGaussianDistribution::LogProbability( const arma::vec& observation) const { const size_t k = observation.n_elem; @@ -46,7 +49,7 @@ double DiagonalGaussianDistribution::LogProbability( return -0.5 * k * log2pi - 0.5 * logDetCov - 0.5 * logExponent(0); } -void DiagonalGaussianDistribution::LogProbability( +inline void DiagonalGaussianDistribution::LogProbability( const arma::mat& observations, arma::vec& logProbabilities) const { @@ -63,12 +66,12 @@ void DiagonalGaussianDistribution::LogProbability( logProbabilities = -0.5 * k * log2pi - 0.5 * logDetCov + logExponents; } -arma::vec DiagonalGaussianDistribution::Random() const +inline arma::vec DiagonalGaussianDistribution::Random() const { return (arma::sqrt(covariance) % arma::randn(mean.n_elem)) + mean; } -void DiagonalGaussianDistribution::Train(const arma::mat& observations) +inline void DiagonalGaussianDistribution::Train(const arma::mat& observations) { if (observations.n_cols > 1) { @@ -95,8 +98,8 @@ void DiagonalGaussianDistribution::Train(const arma::mat& observations) logDetCov = arma::accu(log(covariance)); } -void DiagonalGaussianDistribution::Train(const arma::mat& observations, - const arma::vec& probabilities) +inline void DiagonalGaussianDistribution::Train(const arma::mat& observations, + const arma::vec& probabilities) { if (observations.n_cols > 0) { @@ -146,3 +149,8 @@ void DiagonalGaussianDistribution::Train(const arma::mat& observations, invCov = 1 / covariance; logDetCov = arma::accu(log(covariance)); } + +} // namespace distribution +} // namespace mlpack + +#endif diff --git a/src/mlpack/core/dists/discrete_distribution.hpp b/src/mlpack/core/dists/discrete_distribution.hpp index bf209bb3d0..1321ee49ab 100644 --- a/src/mlpack/core/dists/discrete_distribution.hpp +++ b/src/mlpack/core/dists/discrete_distribution.hpp @@ -252,4 +252,7 @@ class DiscreteDistribution } // namespace distribution } // namespace mlpack +// Include implementation. +#include "discrete_distribution_impl.hpp" + #endif diff --git a/src/mlpack/core/dists/discrete_distribution.cpp b/src/mlpack/core/dists/discrete_distribution_impl.hpp similarity index 88% rename from src/mlpack/core/dists/discrete_distribution.cpp rename to src/mlpack/core/dists/discrete_distribution_impl.hpp index 2fd64aea73..7e3c6db1f4 100644 --- a/src/mlpack/core/dists/discrete_distribution.cpp +++ b/src/mlpack/core/dists/discrete_distribution_impl.hpp @@ -1,5 +1,5 @@ /** - * @file core/dists/discrete_distribution.cpp + * @file core/dists/discrete_distribution_impl.hpp * @author Ryan Curtin * @author Rohan Raj * @@ -10,16 +10,19 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_CORE_DISTRIBUTIONS_DISCRETE_DISTRIBUTION_IMPL_HPP +#define MLPACK_CORE_DISTRIBUTIONS_DISCRETE_DISTRIBUTION_IMPL_HPP + #include "discrete_distribution.hpp" -using namespace mlpack; -using namespace mlpack::distribution; +namespace mlpack { +namespace distribution /** Probability distributions. */ { /** * Return a randomly generated observation according to the probability * distribution defined by this object. */ -arma::vec DiscreteDistribution::Random() const +inline arma::vec DiscreteDistribution::Random() const { size_t dimension = probabilities.size(); arma::vec result(dimension); @@ -53,7 +56,7 @@ arma::vec DiscreteDistribution::Random() const /** * Estimate the probability distribution directly from the given observations. */ -void DiscreteDistribution::Train(const arma::mat& observations) +inline void DiscreteDistribution::Train(const arma::mat& observations) { // Make sure the observations have same dimension as the probabilities. if (observations.n_rows != probabilities.size()) @@ -107,8 +110,8 @@ void DiscreteDistribution::Train(const arma::mat& observations) * Estimate the probability distribution from the given observations when also * given probabilities that each observation is from this distribution. */ -void DiscreteDistribution::Train(const arma::mat& observations, - const arma::vec& probObs) +inline void DiscreteDistribution::Train(const arma::mat& observations, + const arma::vec& probObs) { // Make sure the observations have same dimension as the probabilities. if (observations.n_rows != probabilities.size()) @@ -158,3 +161,8 @@ void DiscreteDistribution::Train(const arma::mat& observations, probabilities[i].fill(1.0 / probabilities[i].n_elem); } } + +} // namespace distribution +} // namespace mlpack + +#endif diff --git a/src/mlpack/core/dists/gamma_distribution.hpp b/src/mlpack/core/dists/gamma_distribution.hpp index ef8b43a1f0..7c7e97def6 100644 --- a/src/mlpack/core/dists/gamma_distribution.hpp +++ b/src/mlpack/core/dists/gamma_distribution.hpp @@ -16,8 +16,8 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ -#ifndef _MLPACK_CORE_DISTRIBUTIONS_GAMMA_DISTRIBUTION_HPP -#define _MLPACK_CORE_DISTRIBUTIONS_GAMMA_DISTRIBUTION_HPP +#ifndef MLPACK_CORE_DISTRIBUTIONS_GAMMA_DISTRIBUTION_HPP +#define MLPACK_CORE_DISTRIBUTIONS_GAMMA_DISTRIBUTION_HPP #include #include @@ -25,7 +25,7 @@ #include namespace mlpack { -namespace distribution { +namespace distribution /** Probability distributions. */ { /** * This class represents the Gamma distribution. It supports training a Gamma @@ -232,4 +232,7 @@ class GammaDistribution } // namespace distribution } // namespace mlpack +// Include implementation. +#include "gamma_distribution_impl.hpp" + #endif diff --git a/src/mlpack/core/dists/gamma_distribution.cpp b/src/mlpack/core/dists/gamma_distribution_impl.hpp similarity index 80% rename from src/mlpack/core/dists/gamma_distribution.cpp rename to src/mlpack/core/dists/gamma_distribution_impl.hpp index 80d0d0f6c0..6da0f9763e 100644 --- a/src/mlpack/core/dists/gamma_distribution.cpp +++ b/src/mlpack/core/dists/gamma_distribution_impl.hpp @@ -1,5 +1,5 @@ /** - * @file core/dists/gamma_distribution.cpp + * @file core/dists/gamma_distribution_impl.hpp * @author Yannis Mentekidis * @author Rohan Raj * @@ -10,26 +10,29 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_CORE_DISTRIBUTIONS_GAMMA_DISTRIBUTION_IMPL_HPP +#define MLPACK_CORE_DISTRIBUTIONS_GAMMA_DISTRIBUTION_IMPL_HPP + #include "gamma_distribution.hpp" -using namespace mlpack; -using namespace mlpack::distribution; +namespace mlpack { +namespace distribution /** Probability distributions. */ { -GammaDistribution::GammaDistribution(const size_t dimensionality) +inline GammaDistribution::GammaDistribution(const size_t dimensionality) { // Initialize distribution. alpha.zeros(dimensionality); beta.zeros(dimensionality); } -GammaDistribution::GammaDistribution(const arma::mat& data, - const double tol) +inline GammaDistribution::GammaDistribution(const arma::mat& data, + const double tol) { Train(data, tol); } -GammaDistribution::GammaDistribution(const arma::vec& alpha, - const arma::vec& beta) +inline GammaDistribution::GammaDistribution(const arma::vec& alpha, + const arma::vec& beta) { if (beta.n_elem != alpha.n_elem) throw std::runtime_error("Alpha and beta vector dimensions mismatch."); @@ -47,7 +50,7 @@ inline bool GammaDistribution::Converged(const double aOld, } // Fits an alpha and beta parameter to each dimension of the data. -void GammaDistribution::Train(const arma::mat& rdata, const double tol) +inline void GammaDistribution::Train(const arma::mat& rdata, const double tol) { // If fittingSet is empty, nothing to do. if (arma::size(rdata) == arma::size(arma::mat())) @@ -64,9 +67,9 @@ void GammaDistribution::Train(const arma::mat& rdata, const double tol) } // Fits an alpha and beta parameter according to observation probabilities. -void GammaDistribution::Train(const arma::mat& rdata, - const arma::vec& probabilities, - const double tol) +inline void GammaDistribution::Train(const arma::mat& rdata, + const arma::vec& probabilities, + const double tol) { // If fittingSet is empty, nothing to do. if (arma::size(rdata) == arma::size(arma::mat())) @@ -94,10 +97,10 @@ void GammaDistribution::Train(const arma::mat& rdata, } // Fits an alpha and beta parameter to each dimension of the data. -void GammaDistribution::Train(const arma::vec& logMeanxVec, - const arma::vec& meanLogxVec, - const arma::vec& meanxVec, - const double tol) +inline void GammaDistribution::Train(const arma::vec& logMeanxVec, + const arma::vec& meanLogxVec, + const arma::vec& meanxVec, + const double tol) { using std::log; @@ -155,8 +158,8 @@ void GammaDistribution::Train(const arma::vec& logMeanxVec, } // Returns the probability of the provided observations. -void GammaDistribution::Probability(const arma::mat& observations, - arma::vec& probabilities) const +inline void GammaDistribution::Probability(const arma::mat& observations, + arma::vec& probabilities) const { size_t numObs = observations.n_cols; @@ -184,15 +187,16 @@ void GammaDistribution::Probability(const arma::mat& observations, // Returns the probability of one observation (x) for one of the Gamma's // dimensions. -double GammaDistribution::Probability(double x, size_t dim) const +inline double GammaDistribution::Probability(double x, size_t dim) const { return std::pow(x, alpha(dim) - 1) * std::exp(-x / beta(dim)) / (std::tgamma(alpha(dim)) * std::pow(beta(dim), alpha(dim))); } // Returns the log probability of the provided observations. -void GammaDistribution::LogProbability(const arma::mat& observations, - arma::vec& logProbabilities) const +inline void GammaDistribution::LogProbability( + const arma::mat& observations, + arma::vec& logProbabilities) const { size_t numObs = observations.n_cols; @@ -221,14 +225,14 @@ void GammaDistribution::LogProbability(const arma::mat& observations, // Returns the log probability of one observation (x) for one of the Gamma's // dimensions. -double GammaDistribution::LogProbability(double x, size_t dim) const +inline double GammaDistribution::LogProbability(double x, size_t dim) const { return std::log(std::pow(x, alpha(dim) - 1) * std::exp(-x / beta(dim)) / (std::tgamma(alpha(dim)) * std::pow(beta(dim), alpha(dim)))); } // Returns a gamma-random d-dimensional vector. -arma::vec GammaDistribution::Random() const +inline arma::vec GammaDistribution::Random() const { arma::vec randVec(alpha.n_elem); @@ -236,8 +240,13 @@ arma::vec GammaDistribution::Random() const { std::gamma_distribution dist(alpha(d), beta(d)); // Use the mlpack random object. - randVec(d) = dist(mlpack::math::randGen); + randVec(d) = dist(mlpack::math::RandGen()); } return randVec; } + +} // namespace distribution +} // namespace mlpack + +#endif diff --git a/src/mlpack/core/dists/gaussian_distribution.hpp b/src/mlpack/core/dists/gaussian_distribution.hpp index 2d47f7d686..6bd1119254 100644 --- a/src/mlpack/core/dists/gaussian_distribution.hpp +++ b/src/mlpack/core/dists/gaussian_distribution.hpp @@ -16,7 +16,7 @@ #include namespace mlpack { -namespace distribution { +namespace distribution /** Probability distributions. */ { /** * A single multivariate Gaussian distribution. @@ -194,4 +194,7 @@ class GaussianDistribution } // namespace distribution } // namespace mlpack +// Include implementation. +#include "gaussian_distribution_impl.hpp" + #endif diff --git a/src/mlpack/core/dists/gaussian_distribution.cpp b/src/mlpack/core/dists/gaussian_distribution_impl.hpp similarity index 80% rename from src/mlpack/core/dists/gaussian_distribution.cpp rename to src/mlpack/core/dists/gaussian_distribution_impl.hpp index 8d4f496012..3ff9d5cb99 100644 --- a/src/mlpack/core/dists/gaussian_distribution.cpp +++ b/src/mlpack/core/dists/gaussian_distribution_impl.hpp @@ -1,5 +1,5 @@ /** - * @file core/dists/gaussian_distribution.cpp + * @file core/dists/gaussian_distribution_impl.hpp * @author Ryan Curtin * @author Michael Fox * @@ -10,33 +10,37 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_CORE_DISTRIBUTIONS_GAUSSIAN_DISTRIBUTION_IMPL_HPP +#define MLPACK_CORE_DISTRIBUTIONS_GAUSSIAN_DISTRIBUTION_IMPL_HPP + #include "gaussian_distribution.hpp" #include -using namespace mlpack; -using namespace mlpack::distribution; +namespace mlpack { +namespace distribution /** Probability distributions. */ { - -GaussianDistribution::GaussianDistribution(const arma::vec& mean, - const arma::mat& covariance) - : mean(mean), logDetCov(0.0) +inline GaussianDistribution::GaussianDistribution( + const arma::vec& mean, + const arma::mat& covariance) : + mean(mean), + logDetCov(0.0) { Covariance(covariance); } -void GaussianDistribution::Covariance(const arma::mat& covariance) +inline void GaussianDistribution::Covariance(const arma::mat& covariance) { this->covariance = covariance; FactorCovariance(); } -void GaussianDistribution::Covariance(arma::mat&& covariance) +inline void GaussianDistribution::Covariance(arma::mat&& covariance) { this->covariance = std::move(covariance); FactorCovariance(); } -void GaussianDistribution::FactorCovariance() +inline void GaussianDistribution::FactorCovariance() { // On Armadillo < 4.500, the "lower" option isn't available. @@ -68,7 +72,8 @@ void GaussianDistribution::FactorCovariance() logDetCov *= 2; } -double GaussianDistribution::LogProbability(const arma::vec& observation) const +inline double GaussianDistribution::LogProbability( + const arma::vec& observation) const { const size_t k = observation.n_elem; const arma::vec diff = mean - observation; @@ -76,7 +81,7 @@ double GaussianDistribution::LogProbability(const arma::vec& observation) const return -0.5 * k * log2pi - 0.5 * logDetCov - 0.5 * v(0); } -arma::vec GaussianDistribution::Random() const +inline arma::vec GaussianDistribution::Random() const { return covLower * arma::randn(mean.n_elem) + mean; } @@ -86,7 +91,7 @@ arma::vec GaussianDistribution::Random() const * * @param observations List of observations. */ -void GaussianDistribution::Train(const arma::mat& observations) +inline void GaussianDistribution::Train(const arma::mat& observations) { if (observations.n_cols > 0) { @@ -95,10 +100,7 @@ void GaussianDistribution::Train(const arma::mat& observations) } else // This will end up just being empty. { - // TODO(stephentu): why do we allow this case? why not throw an error? - mean.zeros(0); - covariance.zeros(0); - return; + Log::Fatal << "Observation columns equal to 0." << std::endl; } // Calculate the mean. @@ -130,8 +132,8 @@ void GaussianDistribution::Train(const arma::mat& observations) * account the probability of each observation actually being from this * distribution. */ -void GaussianDistribution::Train(const arma::mat& observations, - const arma::vec& probabilities) +inline void GaussianDistribution::Train(const arma::mat& observations, + const arma::vec& probabilities) { if (observations.n_cols > 0) { @@ -140,10 +142,7 @@ void GaussianDistribution::Train(const arma::mat& observations, } else // This will end up just being empty. { - // TODO(stephentu): same as above - mean.zeros(0); - covariance.zeros(0); - return; + Log::Fatal << "Observation columns equal to 0." << std::endl; } double sumProb = 0; @@ -185,3 +184,8 @@ void GaussianDistribution::Train(const arma::mat& observations, FactorCovariance(); } + +} // namespace distribution +} // namespace mlpack + +#endif diff --git a/src/mlpack/core/dists/laplace_distribution.hpp b/src/mlpack/core/dists/laplace_distribution.hpp index 991865f4dd..eb651fbb08 100644 --- a/src/mlpack/core/dists/laplace_distribution.hpp +++ b/src/mlpack/core/dists/laplace_distribution.hpp @@ -14,8 +14,10 @@ #ifndef MLPACK_CORE_DISTRIBUTIONS_LAPLACE_DISTRIBUTION_HPP #define MLPACK_CORE_DISTRIBUTIONS_LAPLACE_DISTRIBUTION_HPP +#include + namespace mlpack { -namespace distribution { +namespace distribution /** Probability distributions. */ { /** * The multivariate Laplace distribution centered at 0 has pdf @@ -189,4 +191,7 @@ class LaplaceDistribution } // namespace distribution } // namespace mlpack +// Include implementation. +#include "laplace_distribution_impl.hpp" + #endif diff --git a/src/mlpack/core/dists/laplace_distribution.cpp b/src/mlpack/core/dists/laplace_distribution_impl.hpp similarity index 80% rename from src/mlpack/core/dists/laplace_distribution.cpp rename to src/mlpack/core/dists/laplace_distribution_impl.hpp index 58ef9ba842..1bc41c4945 100644 --- a/src/mlpack/core/dists/laplace_distribution.cpp +++ b/src/mlpack/core/dists/laplace_distribution_impl.hpp @@ -1,5 +1,5 @@ /* - * @file core/dists/laplace_distribution.cpp + * @file core/dists/laplace_distribution_impl.hpp * @author Zhihao Lou * @author Rohan Raj * @@ -10,17 +10,19 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ -#include +#ifndef MLPACK_CORE_DISTRIBUTIONS_LAPLACE_DISTRIBUTION_IMPL_HPP +#define MLPACK_CORE_DISTRIBUTIONS_LAPLACE_DISTRIBUTION_IMPL_HPP #include "laplace_distribution.hpp" -using namespace mlpack; -using namespace mlpack::distribution; +namespace mlpack { +namespace distribution /** Probability distributions. */ { /** * Return the log probability of the given observation. */ -double LaplaceDistribution::LogProbability(const arma::vec& observation) const +inline double LaplaceDistribution::LogProbability( + const arma::vec& observation) const { // Evaluate the PDF of the Laplace distribution to determine // the log probability. @@ -33,8 +35,8 @@ double LaplaceDistribution::LogProbability(const arma::vec& observation) const * @param x List of observations. * @param probabilities Output probabilities for each input observation. */ -void LaplaceDistribution::Probability(const arma::mat& x, - arma::vec& probabilities) const +inline void LaplaceDistribution::Probability(const arma::mat& x, + arma::vec& probabilities) const { probabilities.set_size(x.n_cols); for (size_t i = 0; i < x.n_cols; ++i) @@ -48,7 +50,7 @@ void LaplaceDistribution::Probability(const arma::mat& x, * * @param observations List of observations. */ -void LaplaceDistribution::Estimate(const arma::mat& observations) +inline void LaplaceDistribution::Estimate(const arma::mat& observations) { // The maximum likelihood estimate of the mean is the median of the data for // the univariate case. See the short note "The Double Exponential @@ -81,8 +83,8 @@ void LaplaceDistribution::Estimate(const arma::mat& observations) * taking into account the probability of each observation actually being from * this distribution. */ -void LaplaceDistribution::Estimate(const arma::mat& observations, - const arma::vec& probabilities) +inline void LaplaceDistribution::Estimate(const arma::mat& observations, + const arma::vec& probabilities) { // I am not completely sure that this change results in a valid maximum // likelihood estimator given probabilities of points. @@ -99,3 +101,8 @@ void LaplaceDistribution::Estimate(const arma::mat& observations, scale += probabilities(i) * arma::norm(observations.col(i) - mean, 2); scale /= arma::accu(probabilities); } + +} // namespace distribution +} // namespace mlpack + +#endif diff --git a/src/mlpack/core/dists/regression_distribution.hpp b/src/mlpack/core/dists/regression_distribution.hpp index 03e909d2b5..e109a142fb 100644 --- a/src/mlpack/core/dists/regression_distribution.hpp +++ b/src/mlpack/core/dists/regression_distribution.hpp @@ -18,7 +18,7 @@ #include namespace mlpack { -namespace distribution { +namespace distribution /** Probability distributions. */ { /** * A class that represents a univariate conditionally Gaussian distribution. @@ -160,4 +160,7 @@ class RegressionDistribution } // namespace distribution } // namespace mlpack +// Include implementation. +#include "regression_distribution_impl.hpp" + #endif diff --git a/src/mlpack/core/dists/regression_distribution.cpp b/src/mlpack/core/dists/regression_distribution_impl.hpp similarity index 62% rename from src/mlpack/core/dists/regression_distribution.cpp rename to src/mlpack/core/dists/regression_distribution_impl.hpp index c44ba41200..ce7d3723ee 100644 --- a/src/mlpack/core/dists/regression_distribution.cpp +++ b/src/mlpack/core/dists/regression_distribution_impl.hpp @@ -1,5 +1,5 @@ /** - * @file core/dists/regression_distribution.cpp + * @file core/dists/regression_distribution_impl.hpp * @author Michael Fox * * Implementation of conditional Gaussian distribution for HMM regression @@ -11,17 +11,20 @@ * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_CORE_DISTRIBUTIONS_REGRESSION_DISTRIBUTION_IMPL_HPP +#define MLPACK_CORE_DISTRIBUTIONS_REGRESSION_DISTRIBUTION_IMPL_HPP + #include "regression_distribution.hpp" -using namespace mlpack; -using namespace mlpack::distribution; +namespace mlpack { +namespace distribution /** Probability distributions. */ { /** * Estimate parameters using provided observation weights. * * @param observations List of observations. */ -void RegressionDistribution::Train(const arma::mat& observations) +inline void RegressionDistribution::Train(const arma::mat& observations) { regression::LinearRegression lr(observations.rows(1, observations.n_rows - 1), arma::rowvec(observations.row(0)), 0, true); @@ -36,14 +39,14 @@ void RegressionDistribution::Train(const arma::mat& observations) * * @param weights Probability that given observation is from distribution. */ -void RegressionDistribution::Train(const arma::mat& observations, - const arma::vec& weights) +inline void RegressionDistribution::Train(const arma::mat& observations, + const arma::vec& weights) { Train(observations, arma::rowvec(weights.t())); } -void RegressionDistribution::Train(const arma::mat& observations, - const arma::rowvec& weights) +inline void RegressionDistribution::Train(const arma::mat& observations, + const arma::rowvec& weights) { regression::LinearRegression lr(observations.rows(1, observations.n_rows - 1), arma::rowvec(observations.row(0)), weights, 0, true); @@ -58,23 +61,29 @@ void RegressionDistribution::Train(const arma::mat& observations, * * @param observation Point to evaluate probability at. */ -double RegressionDistribution::Probability(const arma::vec& observation) const +inline double RegressionDistribution::Probability( + const arma::vec& observation) const { arma::rowvec fitted; rf.Predict(observation.rows(1, observation.n_rows-1), fitted); return err.Probability(observation(0)-fitted.t()); } -void RegressionDistribution::Predict(const arma::mat& points, - arma::vec& predictions) const +inline void RegressionDistribution::Predict(const arma::mat& points, + arma::vec& predictions) const { arma::rowvec rowPredictions; Predict(points, rowPredictions); predictions = rowPredictions.t(); } -void RegressionDistribution::Predict(const arma::mat& points, - arma::rowvec& predictions) const +inline void RegressionDistribution::Predict(const arma::mat& points, + arma::rowvec& predictions) const { rf.Predict(points, predictions); } + +} // namespace distribution +} // namespace mlpack + +#endif diff --git a/src/mlpack/core/math/CMakeLists.txt b/src/mlpack/core/math/CMakeLists.txt index 26c1e32fed..c001868938 100644 --- a/src/mlpack/core/math/CMakeLists.txt +++ b/src/mlpack/core/math/CMakeLists.txt @@ -3,7 +3,7 @@ set(SOURCES clamp.hpp columns_to_blocks.hpp - columns_to_blocks.cpp + columns_to_blocks_impl.hpp digamma.hpp lin_alg.hpp lin_alg_impl.hpp @@ -14,7 +14,6 @@ set(SOURCES multiply_slices.hpp quantile.hpp random.hpp - random.cpp random_basis.hpp random_basis_impl.hpp range.hpp diff --git a/src/mlpack/core/math/columns_to_blocks.hpp b/src/mlpack/core/math/columns_to_blocks.hpp index a6d7e3a391..5f66643b0a 100644 --- a/src/mlpack/core/math/columns_to_blocks.hpp +++ b/src/mlpack/core/math/columns_to_blocks.hpp @@ -224,4 +224,7 @@ class ColumnsToBlocks } // namespace math } // namespace mlpack +// Include implementation. +#include "columns_to_blocks_impl.hpp" + #endif diff --git a/src/mlpack/core/math/columns_to_blocks.cpp b/src/mlpack/core/math/columns_to_blocks_impl.hpp similarity index 77% rename from src/mlpack/core/math/columns_to_blocks.cpp rename to src/mlpack/core/math/columns_to_blocks_impl.hpp index 6e98f0dbda..a33441782e 100644 --- a/src/mlpack/core/math/columns_to_blocks.cpp +++ b/src/mlpack/core/math/columns_to_blocks_impl.hpp @@ -1,5 +1,5 @@ /** - * @file core/math/columns_to_blocks.cpp + * @file core/math/columns_to_blocks_impl.hpp * @author Tham Ngap Wei * * Implementation of the ColumnsToBlocks class. @@ -9,15 +9,18 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_METHODS_NN_COLUMNS_TO_BLOCKS_IMPL_HPP +#define MLPACK_METHODS_NN_COLUMNS_TO_BLOCKS_IMPL_HPP + #include "columns_to_blocks.hpp" namespace mlpack { namespace math { -ColumnsToBlocks::ColumnsToBlocks(const size_t rows, - const size_t cols, - const size_t blockHeight, - const size_t blockWidth) : +inline ColumnsToBlocks::ColumnsToBlocks(const size_t rows, + const size_t cols, + const size_t blockHeight, + const size_t blockWidth) : blockHeight(blockHeight), blockWidth(blockWidth), bufSize(1), @@ -30,15 +33,16 @@ ColumnsToBlocks::ColumnsToBlocks(const size_t rows, { } -bool ColumnsToBlocks::IsPerfectSquare(const size_t value) const +inline bool ColumnsToBlocks::IsPerfectSquare(const size_t value) const { const size_t root = (size_t) std::round(std::sqrt(value)); return (value == root * root); } -void ColumnsToBlocks::Transform(const arma::mat& maximalInputs, - arma::mat& output) +inline void ColumnsToBlocks::Transform(const arma::mat& maximalInputs, + arma::mat& output) { + //! TODO: Maybe replace std::runtime_error with Log::Fatal. if (!IsPerfectSquare(maximalInputs.n_rows)) { throw std::runtime_error("maximalInputs.n_rows should be perfect square"); @@ -97,3 +101,5 @@ void ColumnsToBlocks::Transform(const arma::mat& maximalInputs, } // namespace math } // namespace mlpack + +#endif diff --git a/src/mlpack/core/math/random.cpp b/src/mlpack/core/math/random.cpp deleted file mode 100644 index d2049ea80b..0000000000 --- a/src/mlpack/core/math/random.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @file core/math/random.cpp - * - * Declarations of global random number generators. - * - * 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. - */ -#include -#include - -namespace mlpack { -namespace math { - -// Global random object. -MLPACK_EXPORT std::mt19937 randGen; -// Global uniform distribution. -MLPACK_EXPORT std::uniform_real_distribution<> randUniformDist(0.0, 1.0); -// Global normal distribution. -MLPACK_EXPORT std::normal_distribution<> randNormalDist(0.0, 1.0); - -} // namespace math -} // namespace mlpack diff --git a/src/mlpack/core/math/random.hpp b/src/mlpack/core/math/random.hpp index 5091a08ff3..613f966fb2 100644 --- a/src/mlpack/core/math/random.hpp +++ b/src/mlpack/core/math/random.hpp @@ -23,12 +23,26 @@ namespace math /** Miscellaneous math routines. */ { * correctly on Windows. */ -// Global random object. -extern MLPACK_EXPORT std::mt19937 randGen; -// Global uniform distribution. -extern MLPACK_EXPORT std::uniform_real_distribution<> randUniformDist; -// Global normal distribution. -extern MLPACK_EXPORT std::normal_distribution<> randNormalDist; +//! Global random object. +inline std::mt19937& RandGen() +{ + static thread_local std::mt19937 randGen; + return randGen; +} + +//! Global uniform distribution. +inline std::uniform_real_distribution<>& RandUniformDist() +{ + static thread_local std::uniform_real_distribution<> randUniformDist(0.0, 1.0); + return randUniformDist; +} + +//! Global normal distribution. +inline std::normal_distribution<>& RandNormalDist() +{ + static thread_local std::normal_distribution<> randNormalDist(0.0, 1.0); + return randNormalDist; +} /** * Set the random seed used by the random functions (Random() and RandInt()). @@ -40,7 +54,7 @@ extern MLPACK_EXPORT std::normal_distribution<> randNormalDist; inline void RandomSeed(const size_t seed) { #if (!defined(BINDING_TYPE) || BINDING_TYPE != BINDING_TYPE_TEST) - randGen.seed((uint32_t) seed); + RandGen().seed((uint32_t) seed); #if (BINDING_TYPE == BINDING_TYPE_R) // To suppress Found 'srand', possibly from 'srand' (C). (void) seed; @@ -64,14 +78,14 @@ inline void RandomSeed(const size_t seed) inline void FixedRandomSeed() { const static size_t seed = rand(); - randGen.seed((uint32_t) seed); + RandGen().seed((uint32_t) seed); srand((unsigned int) seed); arma::arma_rng::set_seed(seed); } inline void CustomRandomSeed(const size_t seed) { - randGen.seed((uint32_t) seed); + RandGen().seed((uint32_t) seed); srand((unsigned int) seed); arma::arma_rng::set_seed(seed); } @@ -82,7 +96,7 @@ inline void CustomRandomSeed(const size_t seed) */ inline double Random() { - return randUniformDist(randGen); + return RandUniformDist()(RandGen()); } /** @@ -90,7 +104,7 @@ inline double Random() */ inline double Random(const double lo, const double hi) { - return lo + (hi - lo) * randUniformDist(randGen); + return lo + (hi - lo) * RandUniformDist()(RandGen()); } /** @@ -109,7 +123,7 @@ inline double RandBernoulli(const double input) */ inline int RandInt(const int hiExclusive) { - return (int) std::floor((double) hiExclusive * randUniformDist(randGen)); + return (int) std::floor((double) hiExclusive * RandUniformDist()(RandGen())); } /** @@ -118,7 +132,7 @@ inline int RandInt(const int hiExclusive) inline int RandInt(const int lo, const int hiExclusive) { return lo + (int) std::floor((double) (hiExclusive - lo) - * randUniformDist(randGen)); + * RandUniformDist()(RandGen())); } /** @@ -126,7 +140,7 @@ inline int RandInt(const int lo, const int hiExclusive) */ inline double RandNormal() { - return randNormalDist(randGen); + return RandNormalDist()(RandGen()); } /** @@ -138,7 +152,7 @@ inline double RandNormal() */ inline double RandNormal(const double mean, const double variance) { - return variance * randNormalDist(randGen) + mean; + return variance * RandNormalDist()(RandGen()) + mean; } /** diff --git a/src/mlpack/core/tree/CMakeLists.txt b/src/mlpack/core/tree/CMakeLists.txt index ebba35b1cc..d5e49d08a4 100644 --- a/src/mlpack/core/tree/CMakeLists.txt +++ b/src/mlpack/core/tree/CMakeLists.txt @@ -32,7 +32,7 @@ set(SOURCES cellbound.hpp cellbound_impl.hpp cosine_tree/cosine_tree.hpp - cosine_tree/cosine_tree.cpp + cosine_tree/cosine_tree_impl.hpp cover_tree.hpp cover_tree/cover_tree.hpp cover_tree/cover_tree_impl.hpp diff --git a/src/mlpack/core/tree/cosine_tree/cosine_tree.hpp b/src/mlpack/core/tree/cosine_tree/cosine_tree.hpp index 9a295ad025..b1ba2ab1b6 100644 --- a/src/mlpack/core/tree/cosine_tree/cosine_tree.hpp +++ b/src/mlpack/core/tree/cosine_tree/cosine_tree.hpp @@ -13,6 +13,8 @@ #define MLPACK_CORE_TREE_COSINE_TREE_COSINE_TREE_HPP #include +#include +#include namespace mlpack { namespace tree { @@ -285,4 +287,7 @@ class CompareCosineNode } // namespace tree } // namespace mlpack +// Include implementation. +#include "cosine_tree_impl.hpp" + #endif diff --git a/src/mlpack/core/tree/cosine_tree/cosine_tree.cpp b/src/mlpack/core/tree/cosine_tree/cosine_tree_impl.hpp similarity index 89% rename from src/mlpack/core/tree/cosine_tree/cosine_tree.cpp rename to src/mlpack/core/tree/cosine_tree/cosine_tree_impl.hpp index a857bca177..ce02357576 100644 --- a/src/mlpack/core/tree/cosine_tree/cosine_tree.cpp +++ b/src/mlpack/core/tree/cosine_tree/cosine_tree_impl.hpp @@ -1,5 +1,5 @@ /** - * @file core/tree/cosine_tree/cosine_tree.cpp + * @file core/tree/cosine_tree/cosine_tree_impl.hpp * @author Siddharth Agrawal * * Implementation of cosine tree. @@ -9,15 +9,15 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ -#include "cosine_tree.hpp" -#include +#ifndef MLPACK_CORE_TREE_COSINE_TREE_COSINE_TREE_IMPL_HPP +#define MLPACK_CORE_TREE_COSINE_TREE_COSINE_TREE_IMPL_HPP -#include +#include "cosine_tree.hpp" namespace mlpack { namespace tree { -CosineTree::CosineTree(const arma::mat& dataset) : +inline CosineTree::CosineTree(const arma::mat& dataset) : dataset(&dataset), parent(NULL), left(NULL), @@ -46,8 +46,8 @@ CosineTree::CosineTree(const arma::mat& dataset) : splitPointIndex = ColumnSampleLS(); } -CosineTree::CosineTree(CosineTree& parentNode, - const std::vector& subIndices) : +inline CosineTree::CosineTree(CosineTree& parentNode, + const std::vector& subIndices) : dataset(&parentNode.GetDataset()), parent(&parentNode), left(NULL), @@ -75,9 +75,9 @@ CosineTree::CosineTree(CosineTree& parentNode, splitPointIndex = ColumnSampleLS(); } -CosineTree::CosineTree(const arma::mat& dataset, - const double epsilon, - const double delta) : +inline CosineTree::CosineTree(const arma::mat& dataset, + const double epsilon, + const double delta) : dataset(&dataset), delta(delta), left(NULL), @@ -158,7 +158,7 @@ CosineTree::CosineTree(const arma::mat& dataset, } //! Copy the given tree. -CosineTree::CosineTree(const CosineTree& other) : +inline CosineTree::CosineTree(const CosineTree& other) : // Copy matrix, but only if we are the root. dataset((other.parent == NULL) ? new arma::mat(*other.dataset) : NULL), delta(other.delta), @@ -211,7 +211,7 @@ CosineTree::CosineTree(const CosineTree& other) : } //! Copy assignment operator: copy the given other tree. -CosineTree& CosineTree::operator=(const CosineTree& other) +inline CosineTree& CosineTree::operator=(const CosineTree& other) { // Return if it's the same tree. if (this == &other) @@ -279,7 +279,7 @@ CosineTree& CosineTree::operator=(const CosineTree& other) } //! Move the given tree. -CosineTree::CosineTree(CosineTree&& other) : +inline CosineTree::CosineTree(CosineTree&& other) : dataset(other.dataset), delta(std::move(other.delta)), parent(other.parent), @@ -314,7 +314,7 @@ CosineTree::CosineTree(CosineTree&& other) : } //! Move assignment operator: take ownership of the given tree. -CosineTree& CosineTree::operator=(CosineTree&& other) +inline CosineTree& CosineTree::operator=(CosineTree&& other) { // Return if it's the same tree. if (this == &other) @@ -361,7 +361,7 @@ CosineTree& CosineTree::operator=(CosineTree&& other) return *this; } -CosineTree::~CosineTree() +inline CosineTree::~CosineTree() { if (localDataset) delete dataset; @@ -371,10 +371,10 @@ CosineTree::~CosineTree() delete right; } -void CosineTree::ModifiedGramSchmidt(CosineNodeQueue& treeQueue, - arma::vec& centroid, - arma::vec& newBasisVector, - arma::vec* addBasisVector) +inline void CosineTree::ModifiedGramSchmidt(CosineNodeQueue& treeQueue, + arma::vec& centroid, + arma::vec& newBasisVector, + arma::vec* addBasisVector) { // Set new basis vector to centroid. newBasisVector = centroid; @@ -405,10 +405,10 @@ void CosineTree::ModifiedGramSchmidt(CosineNodeQueue& treeQueue, newBasisVector /= arma::norm(newBasisVector, 2); } -double CosineTree::MonteCarloError(CosineTree* node, - CosineNodeQueue& treeQueue, - arma::vec* addBasisVector1, - arma::vec* addBasisVector2) +inline double CosineTree::MonteCarloError(CosineTree* node, + CosineNodeQueue& treeQueue, + arma::vec* addBasisVector1, + arma::vec* addBasisVector2) { std::vector sampledIndices; arma::vec probabilities; @@ -489,7 +489,7 @@ double CosineTree::MonteCarloError(CosineTree* node, return (node->FrobNormSquared() - lowerBound); } -void CosineTree::ConstructBasis(CosineNodeQueue& treeQueue) +inline void CosineTree::ConstructBasis(CosineNodeQueue& treeQueue) { // Initialize basis as matrix of zeros. basis.zeros(dataset->n_rows, treeQueue.size()); @@ -507,7 +507,7 @@ void CosineTree::ConstructBasis(CosineNodeQueue& treeQueue) } } -void CosineTree::CosineNodeSplit() +inline void CosineTree::CosineNodeSplit() { // If less than two points, splitting does not make sense---there is nothing // to split. @@ -544,9 +544,9 @@ void CosineTree::CosineNodeSplit() right = new CosineTree(*this, rightIndices); } -void CosineTree::ColumnSamplesLS(std::vector& sampledIndices, - arma::vec& probabilities, - size_t numSamples) +inline void CosineTree::ColumnSamplesLS(std::vector& sampledIndices, + arma::vec& probabilities, + size_t numSamples) { // Initialize the cumulative distribution vector size. arma::vec cDistribution; @@ -576,7 +576,7 @@ void CosineTree::ColumnSamplesLS(std::vector& sampledIndices, } } -size_t CosineTree::ColumnSampleLS() +inline size_t CosineTree::ColumnSampleLS() { // If only one element is present, there can only be one sample. if (numColumns < 2) @@ -603,10 +603,10 @@ size_t CosineTree::ColumnSampleLS() return BinarySearch(cDistribution, randValue, start, end); } -size_t CosineTree::BinarySearch(arma::vec& cDistribution, - double value, - size_t start, - size_t end) +inline size_t CosineTree::BinarySearch(arma::vec& cDistribution, + double value, + size_t start, + size_t end) { size_t pivot = (start + end) / 2; @@ -631,7 +631,7 @@ size_t CosineTree::BinarySearch(arma::vec& cDistribution, } } -void CosineTree::CalculateCosines(arma::vec& cosines) +inline void CosineTree::CalculateCosines(arma::vec& cosines) { // Initialize cosine vector as a vector of zeros. cosines.zeros(numColumns); @@ -653,7 +653,7 @@ void CosineTree::CalculateCosines(arma::vec& cosines) } } -void CosineTree::CalculateCentroid() +inline void CosineTree::CalculateCentroid() { // Initialize centroid as vector of zeros. centroid.zeros(dataset->n_rows); @@ -668,3 +668,5 @@ void CosineTree::CalculateCentroid() } // namespace tree } // namespace mlpack + +#endif diff --git a/src/mlpack/methods/adaboost/CMakeLists.txt b/src/mlpack/methods/adaboost/CMakeLists.txt index 0a6bf73ceb..3e5d71f191 100644 --- a/src/mlpack/methods/adaboost/CMakeLists.txt +++ b/src/mlpack/methods/adaboost/CMakeLists.txt @@ -4,7 +4,7 @@ set(SOURCES adaboost.hpp adaboost_impl.hpp adaboost_model.hpp - adaboost_model.cpp + adaboost_model_impl.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/adaboost/adaboost_model.hpp b/src/mlpack/methods/adaboost/adaboost_model.hpp index 36743c4e18..23fb651c01 100644 --- a/src/mlpack/methods/adaboost/adaboost_model.hpp +++ b/src/mlpack/methods/adaboost/adaboost_model.hpp @@ -126,4 +126,7 @@ class AdaBoostModel } // namespace adaboost } // namespace mlpack +// Include implementation. +#include "adaboost_model_impl.hpp" + #endif diff --git a/src/mlpack/methods/adaboost/adaboost_model.cpp b/src/mlpack/methods/adaboost/adaboost_model_impl.hpp similarity index 59% rename from src/mlpack/methods/adaboost/adaboost_model.cpp rename to src/mlpack/methods/adaboost/adaboost_model_impl.hpp index d71b857d74..57a68090eb 100644 --- a/src/mlpack/methods/adaboost/adaboost_model.cpp +++ b/src/mlpack/methods/adaboost/adaboost_model_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/adaboost/adaboost_model.cpp + * @file methods/adaboost/adaboost_model_impl.hpp * @author Ryan Curtin * * A serializable AdaBoost model, used by the main program. @@ -9,18 +9,17 @@ * 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_MODEL_IMPL_HPP +#define MLPACK_METHODS_ADABOOST_ADABOOST_MODEL_IMPL_HPP + #include "adaboost.hpp" #include "adaboost_model.hpp" -using namespace mlpack; -using namespace std; -using namespace arma; -using namespace mlpack::adaboost; -using namespace mlpack::tree; -using namespace mlpack::perceptron; +namespace mlpack { +namespace adaboost { //! Create an empty AdaBoost model. -AdaBoostModel::AdaBoostModel() : +inline AdaBoostModel::AdaBoostModel() : weakLearnerType(0), dsBoost(NULL), pBoost(NULL), @@ -30,8 +29,8 @@ AdaBoostModel::AdaBoostModel() : } //! Create the AdaBoost model with the given mappings and type. -AdaBoostModel::AdaBoostModel( - const Col& mappings, +inline AdaBoostModel::AdaBoostModel( + const arma::Col& mappings, const size_t weakLearnerType) : mappings(mappings), weakLearnerType(weakLearnerType), @@ -43,20 +42,20 @@ AdaBoostModel::AdaBoostModel( } //! Copy constructor. -AdaBoostModel::AdaBoostModel(const AdaBoostModel& other) : +inline AdaBoostModel::AdaBoostModel(const AdaBoostModel& other) : mappings(other.mappings), weakLearnerType(other.weakLearnerType), dsBoost(other.dsBoost == NULL ? NULL : - new AdaBoost(*other.dsBoost)), + new AdaBoost(*other.dsBoost)), pBoost(other.pBoost == NULL ? NULL : - new AdaBoost>(*other.pBoost)), + new AdaBoost>(*other.pBoost)), dimensionality(other.dimensionality) { // Nothing to do. } //! Move constructor. -AdaBoostModel::AdaBoostModel(AdaBoostModel&& other) : +inline AdaBoostModel::AdaBoostModel(AdaBoostModel&& other) : mappings(std::move(other.mappings)), weakLearnerType(other.weakLearnerType), dsBoost(other.dsBoost), @@ -70,7 +69,7 @@ AdaBoostModel::AdaBoostModel(AdaBoostModel&& other) : } //! Copy assignment operator. -AdaBoostModel& AdaBoostModel::operator=(const AdaBoostModel& other) +inline AdaBoostModel& AdaBoostModel::operator=(const AdaBoostModel& other) { if (this != &other) { @@ -79,11 +78,11 @@ AdaBoostModel& AdaBoostModel::operator=(const AdaBoostModel& other) delete dsBoost; dsBoost = (other.dsBoost == NULL) ? NULL : - new AdaBoost(*other.dsBoost); + new AdaBoost(*other.dsBoost); delete pBoost; pBoost = (other.pBoost == NULL) ? NULL : - new AdaBoost>(*other.pBoost); + new AdaBoost>(*other.pBoost); dimensionality = other.dimensionality; } @@ -91,7 +90,7 @@ AdaBoostModel& AdaBoostModel::operator=(const AdaBoostModel& other) } //! Move assignment operator. -AdaBoostModel& AdaBoostModel::operator=(AdaBoostModel&& other) +inline AdaBoostModel& AdaBoostModel::operator=(AdaBoostModel&& other) { if (this != &other) { @@ -109,40 +108,40 @@ AdaBoostModel& AdaBoostModel::operator=(AdaBoostModel&& other) return *this; } -AdaBoostModel::~AdaBoostModel() +inline AdaBoostModel::~AdaBoostModel() { delete dsBoost; delete pBoost; } //! Train the model. -void AdaBoostModel::Train(const mat& data, - const Row& labels, - const size_t numClasses, - const size_t iterations, - const double tolerance) +inline void AdaBoostModel::Train(const arma::mat& data, + const arma::Row& labels, + const size_t numClasses, + const size_t iterations, + const double tolerance) { dimensionality = data.n_rows; if (weakLearnerType == WeakLearnerTypes::DECISION_STUMP) { delete dsBoost; - ID3DecisionStump ds(data, labels, max(labels) + 1); - dsBoost = new AdaBoost(data, labels, numClasses, ds, - iterations, tolerance); + tree::ID3DecisionStump ds(data, labels, max(labels) + 1); + dsBoost = new AdaBoost(data, labels, numClasses, + ds, iterations, tolerance); } else if (weakLearnerType == WeakLearnerTypes::PERCEPTRON) { delete pBoost; - Perceptron<> p(data, labels, max(labels) + 1); - pBoost = new AdaBoost>(data, labels, numClasses, p, iterations, - tolerance); + perceptron::Perceptron<> p(data, labels, max(labels) + 1); + pBoost = new AdaBoost>(data, labels, numClasses, + p, iterations, tolerance); } } //! Classify test points. -void AdaBoostModel::Classify(const mat& testData, - Row& predictions, - mat& probabilities) +inline void AdaBoostModel::Classify(const arma::mat& testData, + arma::Row& predictions, + arma::mat& probabilities) { if (weakLearnerType == WeakLearnerTypes::DECISION_STUMP) dsBoost->Classify(testData, predictions, probabilities); @@ -151,11 +150,16 @@ void AdaBoostModel::Classify(const mat& testData, } //! Classify test points. -void AdaBoostModel::Classify(const mat& testData, - Row& predictions) +inline void AdaBoostModel::Classify(const arma::mat& testData, + arma::Row& predictions) { if (weakLearnerType == WeakLearnerTypes::DECISION_STUMP) dsBoost->Classify(testData, predictions); else if (weakLearnerType == WeakLearnerTypes::PERCEPTRON) pBoost->Classify(testData, predictions); } + +} // namespace adaboost +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/bayesian_linear_regression/CMakeLists.txt b/src/mlpack/methods/bayesian_linear_regression/CMakeLists.txt index c12a86af74..a5e8febd38 100644 --- a/src/mlpack/methods/bayesian_linear_regression/CMakeLists.txt +++ b/src/mlpack/methods/bayesian_linear_regression/CMakeLists.txt @@ -3,7 +3,6 @@ set(SOURCES bayesian_linear_regression.hpp bayesian_linear_regression_impl.hpp - bayesian_linear_regression.cpp ) # add directory name to sources diff --git a/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.cpp b/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.cpp deleted file mode 100644 index 33e1c53b5c..0000000000 --- a/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.cpp +++ /dev/null @@ -1,184 +0,0 @@ -/** - * @file methods/bayesian_linear_regression/bayesian_linear_regression.cpp - * @author Clement Mercier - * - * Implementation of Bayesian linear regression. - * - * 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. - */ -#include "bayesian_linear_regression.hpp" -#include -#include - -using namespace mlpack; -using namespace mlpack::regression; - -BayesianLinearRegression::BayesianLinearRegression(const bool centerData, - const bool scaleData, - const size_t maxIterations, - const double tolerance) : - centerData(centerData), - scaleData(scaleData), - maxIterations(maxIterations), - tolerance(tolerance), - responsesOffset(0.0), - alpha(0.0), - beta(0.0), - gamma(0.0) -{/* Nothing to do */} - -double BayesianLinearRegression::Train(const arma::mat& data, - const arma::rowvec& responses) -{ - arma::mat phi; - arma::rowvec t; - arma::colvec eigVal; - arma::mat eigVec; - - // Preprocess the data. Center and scale. - responsesOffset = CenterScaleData(data, responses, phi, t); - - if (!arma::eig_sym(eigVal, eigVec, arma::symmatu(phi * phi.t()))) - { - Log::Fatal << "BayesianLinearRegression::Train(): Eigendecomposition " - << "of covariance failed!" << std::endl; - } - - // Compute this quantities once and for all. - const arma::mat eigVecInv = inv(eigVec); - const arma::colvec eigVecInvPhitT = eigVecInv * phi * t.t(); - - // Initialize the hyperparameters and begin with an infinitely broad prior. - alpha = 1e-6; - beta = 1 / (var(t, 1) * 0.1); - - unsigned short i = 0; - double deltaAlpha = 1.0, crit = 1.0; - - while ((crit > tolerance) && (i < maxIterations)) - { - deltaAlpha = -alpha; - double deltaBeta = -beta; - - // Update the solution. - omega = eigVec * diagmat(1 / (eigVal + (alpha / beta))) * eigVecInvPhitT; - - // Update alpha. - gamma = sum(eigVal / (alpha / beta + eigVal)); - alpha = gamma / dot(omega, omega); - - // Update beta. - const arma::rowvec temp = t - omega.t() * phi; - beta = (data.n_cols - gamma) / dot(temp, temp); - - // Compute the stopping criterion. - deltaAlpha += alpha; - deltaBeta += beta; - crit = std::abs(deltaAlpha / alpha + deltaBeta / beta); - i++; - } - // Compute the covariance matrix for the uncertainties later. - matCovariance = eigVec * diagmat(1 / (beta * eigVal + alpha)) * eigVecInv; - - return RMSE(data, responses); -} - -void BayesianLinearRegression::Predict(const arma::mat& points, - arma::rowvec& predictions) const -{ - // Center and scale the points before applying the model. - arma::mat matX; - CenterScaleDataPred(points, matX); - predictions = omega.t() * matX + responsesOffset; -} - -void BayesianLinearRegression::Predict(const arma::mat& points, - arma::rowvec& predictions, - arma::rowvec& std) const -{ - // Center and scale the points before applying the model. - arma::mat matX; - CenterScaleDataPred(points, matX); - predictions = omega.t() * matX + responsesOffset; - // Compute the standard deviation for each point. - std = sqrt(Variance() + sum(matX % (matCovariance * matX), 0)); -} - -double BayesianLinearRegression::RMSE(const arma::mat& data, - const arma::rowvec& responses) const -{ - arma::rowvec predictions; - Predict(data, predictions); - return sqrt(mean(square(responses - predictions))); -} - -double BayesianLinearRegression::CenterScaleData(const arma::mat& data, - const arma::rowvec& responses, - arma::mat& dataProc, - arma::rowvec& responsesProc) -{ - if (!centerData && !scaleData) - { - dataProc = arma::mat(const_cast(data.memptr()), data.n_rows, - data.n_cols, false, true); - responsesProc = arma::rowvec(const_cast(responses.memptr()), - responses.n_elem, false, - true); - } - - else if (centerData && !scaleData) - { - dataOffset = mean(data, 1); - responsesOffset = mean(responses); - dataProc = data.each_col() - dataOffset; - responsesProc = responses - responsesOffset; - } - - else if (!centerData && scaleData) - { - dataScale = stddev(data, 0, 1); - dataProc = data.each_col() / dataScale; - responsesProc = arma::rowvec(const_cast(responses.memptr()), - responses.n_elem, false, - true); - } - - else - { - dataOffset = mean(data, 1); - dataScale = stddev(data, 0, 1); - responsesOffset = mean(responses); - dataProc = (data.each_col() - dataOffset).each_col() / dataScale; - responsesProc = responses - responsesOffset; - } - return responsesOffset; -} - -void BayesianLinearRegression::CenterScaleDataPred( - const arma::mat& data, - arma::mat& dataProc) const -{ - if (!centerData && !scaleData) - { - dataProc = arma::mat(const_cast(data.memptr()), data.n_rows, - data.n_cols, false, true); - } - - else if (centerData && !scaleData) - { - dataProc = data.each_col() - dataOffset; - } - - else if (!centerData && scaleData) - { - dataProc = data.each_col() / dataScale; - } - - else - { - dataProc = (data.each_col() - dataOffset).each_col() / dataScale; - } -} diff --git a/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.hpp b/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.hpp index 417e795bff..7665c3e9a6 100644 --- a/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.hpp +++ b/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.hpp @@ -16,6 +16,8 @@ #define MLPACK_METHODS_BAYESIAN_LINEAR_REGRESSION_HPP #include +#include +#include namespace mlpack { namespace regression { diff --git a/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression_impl.hpp b/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression_impl.hpp index 65a4d70998..5cfc914623 100644 --- a/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression_impl.hpp +++ b/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression_impl.hpp @@ -17,6 +17,176 @@ namespace mlpack { namespace regression { +inline BayesianLinearRegression::BayesianLinearRegression( + const bool centerData, + const bool scaleData, + const size_t maxIterations, + const double tolerance) : + centerData(centerData), + scaleData(scaleData), + maxIterations(maxIterations), + tolerance(tolerance), + responsesOffset(0.0), + alpha(0.0), + beta(0.0), + gamma(0.0) +{/* Nothing to do */} + +inline double BayesianLinearRegression::Train(const arma::mat& data, + const arma::rowvec& responses) +{ + arma::mat phi; + arma::rowvec t; + arma::colvec eigVal; + arma::mat eigVec; + + // Preprocess the data. Center and scale. + responsesOffset = CenterScaleData(data, responses, phi, t); + + if (!arma::eig_sym(eigVal, eigVec, arma::symmatu(phi * phi.t()))) + { + Log::Fatal << "BayesianLinearRegression::Train(): Eigendecomposition " + << "of covariance failed!" << std::endl; + } + + // Compute this quantities once and for all. + const arma::mat eigVecInv = inv(eigVec); + const arma::colvec eigVecInvPhitT = eigVecInv * phi * t.t(); + + // Initialize the hyperparameters and begin with an infinitely broad prior. + alpha = 1e-6; + beta = 1 / (var(t, 1) * 0.1); + + unsigned short i = 0; + double crit = 1.0; + + while ((crit > tolerance) && (i < maxIterations)) + { + double deltaAlpha = -alpha; + double deltaBeta = -beta; + + // Update the solution. + omega = eigVec * diagmat(1 / (eigVal + (alpha / beta))) * eigVecInvPhitT; + + // Update alpha. + gamma = sum(eigVal / (alpha / beta + eigVal)); + alpha = gamma / dot(omega, omega); + + // Update beta. + const arma::rowvec temp = t - omega.t() * phi; + beta = (data.n_cols - gamma) / dot(temp, temp); + + // Compute the stopping criterion. + deltaAlpha += alpha; + deltaBeta += beta; + crit = std::abs(deltaAlpha / alpha + deltaBeta / beta); + i++; + } + // Compute the covariance matrix for the uncertainties later. + matCovariance = eigVec * diagmat(1 / (beta * eigVal + alpha)) * eigVecInv; + + return RMSE(data, responses); +} + +inline void BayesianLinearRegression::Predict(const arma::mat& points, + arma::rowvec& predictions) const +{ + // Center and scale the points before applying the model. + arma::mat matX; + CenterScaleDataPred(points, matX); + predictions = omega.t() * matX + responsesOffset; +} + +inline void BayesianLinearRegression::Predict(const arma::mat& points, + arma::rowvec& predictions, + arma::rowvec& std) const +{ + // Center and scale the points before applying the model. + arma::mat matX; + CenterScaleDataPred(points, matX); + predictions = omega.t() * matX + responsesOffset; + // Compute the standard deviation for each point. + std = sqrt(Variance() + sum(matX % (matCovariance * matX), 0)); +} + +inline double BayesianLinearRegression::RMSE( + const arma::mat& data, + const arma::rowvec& responses) const +{ + arma::rowvec predictions; + Predict(data, predictions); + return sqrt(mean(square(responses - predictions))); +} + +inline double BayesianLinearRegression::CenterScaleData( + const arma::mat& data, + const arma::rowvec& responses, + arma::mat& dataProc, + arma::rowvec& responsesProc) +{ + if (!centerData && !scaleData) + { + dataProc = arma::mat(const_cast(data.memptr()), data.n_rows, + data.n_cols, false, true); + responsesProc = arma::rowvec(const_cast(responses.memptr()), + responses.n_elem, false, + true); + } + + else if (centerData && !scaleData) + { + dataOffset = mean(data, 1); + responsesOffset = mean(responses); + dataProc = data.each_col() - dataOffset; + responsesProc = responses - responsesOffset; + } + + else if (!centerData && scaleData) + { + dataScale = stddev(data, 0, 1); + dataProc = data.each_col() / dataScale; + responsesProc = arma::rowvec(const_cast(responses.memptr()), + responses.n_elem, false, + true); + } + + else + { + dataOffset = mean(data, 1); + dataScale = stddev(data, 0, 1); + responsesOffset = mean(responses); + dataProc = (data.each_col() - dataOffset).each_col() / dataScale; + responsesProc = responses - responsesOffset; + } + return responsesOffset; +} + +inline void BayesianLinearRegression::CenterScaleDataPred( + const arma::mat& data, + arma::mat& dataProc) const +{ + if (!centerData && !scaleData) + { + dataProc = arma::mat(const_cast(data.memptr()), data.n_rows, + data.n_cols, false, true); + } + + else if (centerData && !scaleData) + { + dataProc = data.each_col() - dataOffset; + } + + else if (!centerData && scaleData) + { + dataProc = data.each_col() / dataScale; + } + + else + { + dataProc = (data.each_col() - dataOffset).each_col() / dataScale; + } +} + /** * Serialize the Bayesian linear regression model. */ diff --git a/src/mlpack/methods/bias_svd/bias_svd_function_impl.hpp b/src/mlpack/methods/bias_svd/bias_svd_function_impl.hpp index e18e7393ad..004638b442 100644 --- a/src/mlpack/methods/bias_svd/bias_svd_function_impl.hpp +++ b/src/mlpack/methods/bias_svd/bias_svd_function_impl.hpp @@ -317,7 +317,7 @@ inline double ParallelSGD::Optimize( if (shuffle) // Determine order of visitation. std::shuffle(visitationOrder.begin(), visitationOrder.end(), - mlpack::math::randGen); + mlpack::math::RandGen()); #pragma omp parallel { diff --git a/src/mlpack/methods/block_krylov_svd/CMakeLists.txt b/src/mlpack/methods/block_krylov_svd/CMakeLists.txt index 6380befb28..fef06ef13d 100644 --- a/src/mlpack/methods/block_krylov_svd/CMakeLists.txt +++ b/src/mlpack/methods/block_krylov_svd/CMakeLists.txt @@ -2,7 +2,7 @@ # Anything not in this list will not be compiled into mlpack. set(SOURCES randomized_block_krylov_svd.hpp - randomized_block_krylov_svd.cpp + randomized_block_krylov_svd_impl.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd.hpp b/src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd.hpp index 0979cf4613..cae4c7e89f 100644 --- a/src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd.hpp +++ b/src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd.hpp @@ -125,4 +125,7 @@ class RandomizedBlockKrylovSVD } // namespace svd } // namespace mlpack +// Include implementation. +#include "randomized_block_krylov_svd_impl.hpp" + #endif diff --git a/src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd.cpp b/src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd_impl.hpp similarity index 68% rename from src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd.cpp rename to src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd_impl.hpp index 101d7cd477..e908065129 100644 --- a/src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd.cpp +++ b/src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/block_krylov_svd/randomized_block_krylov_svd.cpp + * @file methods/block_krylov_svd/randomized_block_krylov_svd_impl.hpp * @author Marcus Edel * * Implementation of the randomized block krylov SVD method. @@ -9,19 +9,22 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_METHODS_BLOCK_KRYLOV_SVD_RANDOMIZED_BLOCK_KRYLOV_SVD_IMPL_HPP +#define MLPACK_METHODS_BLOCK_KRYLOV_SVD_RANDOMIZED_BLOCK_KRYLOV_SVD_IMPL_HPP #include "randomized_block_krylov_svd.hpp" namespace mlpack { namespace svd { -RandomizedBlockKrylovSVD::RandomizedBlockKrylovSVD(const arma::mat& data, - arma::mat& u, - arma::vec& s, - arma::mat& v, - const size_t maxIterations, - const size_t rank, - const size_t blockSize) : +inline RandomizedBlockKrylovSVD::RandomizedBlockKrylovSVD( + const arma::mat& data, + arma::mat& u, + arma::vec& s, + arma::mat& v, + const size_t maxIterations, + const size_t rank, + const size_t blockSize) : maxIterations(maxIterations), blockSize(blockSize) { @@ -35,19 +38,20 @@ RandomizedBlockKrylovSVD::RandomizedBlockKrylovSVD(const arma::mat& data, } } -RandomizedBlockKrylovSVD::RandomizedBlockKrylovSVD(const size_t maxIterations, - const size_t blockSize) : +inline RandomizedBlockKrylovSVD::RandomizedBlockKrylovSVD( + const size_t maxIterations, + const size_t blockSize) : maxIterations(maxIterations), blockSize(blockSize) { /* Nothing to do here */ } -void RandomizedBlockKrylovSVD::Apply(const arma::mat& data, - arma::mat& u, - arma::vec& s, - arma::mat& v, - const size_t rank) +inline void RandomizedBlockKrylovSVD::Apply(const arma::mat& data, + arma::mat& u, + arma::vec& s, + arma::mat& v, + const size_t rank) { arma::mat Q, R, block, blockIteration; @@ -94,3 +98,5 @@ void RandomizedBlockKrylovSVD::Apply(const arma::mat& data, } // namespace svd } // namespace mlpack + +#endif diff --git a/src/mlpack/methods/cf/CMakeLists.txt b/src/mlpack/methods/cf/CMakeLists.txt index 1cf4e79e5d..3032109856 100644 --- a/src/mlpack/methods/cf/CMakeLists.txt +++ b/src/mlpack/methods/cf/CMakeLists.txt @@ -5,7 +5,6 @@ set(SOURCES cf_impl.hpp cf_model.hpp cf_model_impl.hpp - cf_model.cpp svd_wrapper.hpp svd_wrapper_impl.hpp ) diff --git a/src/mlpack/methods/cf/cf_model.cpp b/src/mlpack/methods/cf/cf_model.cpp deleted file mode 100644 index 424726953e..0000000000 --- a/src/mlpack/methods/cf/cf_model.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/** - * @file methods/cf/cf_model_impl.hpp - * @author Wenhao Huang - * - * A serializable CF model, used by the main program. - * - * 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. - */ -#include "cf_model.hpp" - -namespace mlpack { -namespace cf { - -CFModel::CFModel() : - decompositionType(NMF), - normalizationType(NO_NORMALIZATION), - cf(NULL) -{ - // Nothing else to do. -} - -CFModel::CFModel(const CFModel& other) : - decompositionType(other.decompositionType), - normalizationType(other.normalizationType), - cf(other.cf->Clone()) -{ - // Nothing else to do. -} - -CFModel::CFModel(CFModel&& other) : - decompositionType(other.decompositionType), - normalizationType(other.normalizationType), - cf(std::move(other.cf)) -{ - // Reset properties of the other one. - other.decompositionType = NMF; - other.normalizationType = NO_NORMALIZATION; -} - -CFModel& CFModel::operator=(const CFModel& other) -{ - if (this != &other) - { - decompositionType = other.decompositionType; - normalizationType = other.normalizationType; - cf = other.cf->Clone(); - } - - return *this; -} - -CFModel& CFModel::operator=(CFModel&& other) -{ - if (this != &other) - { - decompositionType = other.decompositionType; - normalizationType = other.normalizationType; - cf = std::move(other.cf); - - // Reset the other object. - other.decompositionType = NMF; - other.normalizationType = NO_NORMALIZATION; - } - - return *this; -} - -CFModel::~CFModel() -{ - delete cf; -} - -template -CFWrapperBase* TrainHelper(const DecompositionPolicy& decomposition, - const CFModel::NormalizationTypes normalizationType, - const arma::mat& data, - const size_t numUsersForSimilarity, - const size_t rank, - const size_t maxIterations, - const double minResidue, - const bool mit) -{ - switch (normalizationType) - { - case CFModel::NO_NORMALIZATION: - return new CFWrapper(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - - case CFModel::ITEM_MEAN_NORMALIZATION: - return new CFWrapper(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - - case CFModel::USER_MEAN_NORMALIZATION: - return new CFWrapper(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - - case CFModel::OVERALL_MEAN_NORMALIZATION: - return new CFWrapper(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - - case CFModel::Z_SCORE_NORMALIZATION: - return new CFWrapper(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - } - - // This shouldn't ever happen. - return NULL; -} - -void CFModel::Train(const arma::mat& data, - const size_t numUsersForSimilarity, - const size_t rank, - const size_t maxIterations, - const double minResidue, - const bool mit) -{ - // Delete the current CFType object, if there is one. - delete cf; - - switch (decompositionType) - { - case NMF: - cf = TrainHelper(NMFPolicy(), normalizationType, data, - numUsersForSimilarity, rank, maxIterations, minResidue, mit); - break; - - case BATCH_SVD: - cf = TrainHelper(BatchSVDPolicy(), normalizationType, data, - numUsersForSimilarity, rank, maxIterations, minResidue, mit); - break; - - case RANDOMIZED_SVD: - cf = TrainHelper(RandomizedSVDPolicy(), normalizationType, data, - numUsersForSimilarity, rank, maxIterations, minResidue, mit); - break; - - case REG_SVD: - cf = TrainHelper(RegSVDPolicy(), normalizationType, data, - numUsersForSimilarity, rank, maxIterations, minResidue, mit); - break; - - case SVD_COMPLETE: - cf = TrainHelper(SVDCompletePolicy(), normalizationType, data, - numUsersForSimilarity, rank, maxIterations, minResidue, mit); - break; - - case SVD_INCOMPLETE: - cf = TrainHelper(SVDIncompletePolicy(), normalizationType, data, - numUsersForSimilarity, rank, maxIterations, minResidue, mit); - break; - - case BIAS_SVD: - cf = TrainHelper(BiasSVDPolicy(), normalizationType, data, - numUsersForSimilarity, rank, maxIterations, minResidue, mit); - break; - - case SVD_PLUS_PLUS: - cf = TrainHelper(SVDPlusPlusPolicy(), normalizationType, data, - numUsersForSimilarity, rank, maxIterations, minResidue, mit); - break; - } -} - -//! Make predictions. -void CFModel::Predict(const NeighborSearchTypes nsType, - const InterpolationTypes interpolationType, - const arma::Mat& combinations, - arma::vec& predictions) -{ - cf->Predict(nsType, interpolationType, combinations, predictions); -} - -//! Compute recommendations for queried users. -void CFModel::GetRecommendations(const NeighborSearchTypes nsType, - const InterpolationTypes interpolationType, - const size_t numRecs, - arma::Mat& recommendations, - const arma::Col& users) -{ - cf->GetRecommendations(nsType, interpolationType, numRecs, recommendations, - users); -} - -//! Compute recommendations for all users. -void CFModel::GetRecommendations(const NeighborSearchTypes nsType, - const InterpolationTypes interpolationType, - const size_t numRecs, - arma::Mat& recommendations) -{ - cf->GetRecommendations(nsType, interpolationType, numRecs, recommendations); -} - -} // namespace cf -} // namespace mlpack diff --git a/src/mlpack/methods/cf/cf_model_impl.hpp b/src/mlpack/methods/cf/cf_model_impl.hpp index fa2634a823..0ed1d55077 100644 --- a/src/mlpack/methods/cf/cf_model_impl.hpp +++ b/src/mlpack/methods/cf/cf_model_impl.hpp @@ -40,6 +40,107 @@ namespace mlpack { namespace cf { +inline CFModel::CFModel() : + decompositionType(NMF), + normalizationType(NO_NORMALIZATION), + cf(NULL) +{ + // Nothing else to do. +} + +inline CFModel::CFModel(const CFModel& other) : + decompositionType(other.decompositionType), + normalizationType(other.normalizationType), + cf(other.cf->Clone()) +{ + // Nothing else to do. +} + +inline CFModel::CFModel(CFModel&& other) : + decompositionType(other.decompositionType), + normalizationType(other.normalizationType), + cf(std::move(other.cf)) +{ + // Reset properties of the other one. + other.decompositionType = NMF; + other.normalizationType = NO_NORMALIZATION; +} + +inline CFModel& CFModel::operator=(const CFModel& other) +{ + if (this != &other) + { + decompositionType = other.decompositionType; + normalizationType = other.normalizationType; + cf = other.cf->Clone(); + } + + return *this; +} + +inline CFModel& CFModel::operator=(CFModel&& other) +{ + if (this != &other) + { + decompositionType = other.decompositionType; + normalizationType = other.normalizationType; + cf = std::move(other.cf); + + // Reset the other object. + other.decompositionType = NMF; + other.normalizationType = NO_NORMALIZATION; + } + + return *this; +} + +inline CFModel::~CFModel() +{ + delete cf; +} + +template +CFWrapperBase* TrainHelper(const DecompositionPolicy& decomposition, + const CFModel::NormalizationTypes normalizationType, + const arma::mat& data, + const size_t numUsersForSimilarity, + const size_t rank, + const size_t maxIterations, + const double minResidue, + const bool mit) +{ + switch (normalizationType) + { + case CFModel::NO_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + + case CFModel::ITEM_MEAN_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + + case CFModel::USER_MEAN_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + + case CFModel::OVERALL_MEAN_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + + case CFModel::Z_SCORE_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + } + + // This shouldn't ever happen. + return NULL; +} + template void PredictHelper(CFType& cf, const InterpolationTypes interpolationType, @@ -321,6 +422,93 @@ void SerializeHelper(Archive& ar, } } +inline void CFModel::Train( + const arma::mat& data, + const size_t numUsersForSimilarity, + const size_t rank, + const size_t maxIterations, + const double minResidue, + const bool mit) +{ + // Delete the current CFType object, if there is one. + delete cf; + + switch (decompositionType) + { + case NMF: + cf = TrainHelper(NMFPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case BATCH_SVD: + cf = TrainHelper(BatchSVDPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case RANDOMIZED_SVD: + cf = TrainHelper(RandomizedSVDPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case REG_SVD: + cf = TrainHelper(RegSVDPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case SVD_COMPLETE: + cf = TrainHelper(SVDCompletePolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case SVD_INCOMPLETE: + cf = TrainHelper(SVDIncompletePolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case BIAS_SVD: + cf = TrainHelper(BiasSVDPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case SVD_PLUS_PLUS: + cf = TrainHelper(SVDPlusPlusPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + } +} + +//! Make predictions. +inline void CFModel::Predict( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const arma::Mat& combinations, + arma::vec& predictions) +{ + cf->Predict(nsType, interpolationType, combinations, predictions); +} + +//! Compute recommendations for queried users. +inline void CFModel::GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations, + const arma::Col& users) +{ + cf->GetRecommendations(nsType, interpolationType, numRecs, recommendations, + users); +} + +//! Compute recommendations for all users. +inline void CFModel::GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations) +{ + cf->GetRecommendations(nsType, interpolationType, numRecs, recommendations); +} + template void CFModel::serialize(Archive& ar, const uint32_t /* version */) { diff --git a/src/mlpack/methods/fastmks/CMakeLists.txt b/src/mlpack/methods/fastmks/CMakeLists.txt index cefe42a02e..8e1bc0ad22 100644 --- a/src/mlpack/methods/fastmks/CMakeLists.txt +++ b/src/mlpack/methods/fastmks/CMakeLists.txt @@ -5,7 +5,6 @@ set(SOURCES fastmks_impl.hpp fastmks_model.hpp fastmks_model_impl.hpp - fastmks_model.cpp fastmks_rules.hpp fastmks_rules_impl.hpp fastmks_stat.hpp diff --git a/src/mlpack/methods/fastmks/fastmks_model.cpp b/src/mlpack/methods/fastmks/fastmks_model.cpp deleted file mode 100644 index f7f3e9f126..0000000000 --- a/src/mlpack/methods/fastmks/fastmks_model.cpp +++ /dev/null @@ -1,318 +0,0 @@ -/** - * @file methods/fastmks/fastmks_model.cpp - * @author Ryan Curtin - * - * Implementation of non-templatized functions of FastMKSModel. - * - * 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. - */ -#include "fastmks_model.hpp" - -using namespace mlpack; -using namespace mlpack::fastmks; -using namespace mlpack::kernel; - -FastMKSModel::FastMKSModel(const int kernelType) : - kernelType(kernelType), - linear(NULL), - polynomial(NULL), - cosine(NULL), - gaussian(NULL), - epan(NULL), - triangular(NULL), - hyptan(NULL) -{ - // Nothing to do. -} - -FastMKSModel::FastMKSModel(const FastMKSModel& other) : - kernelType(other.kernelType), - linear(other.linear == NULL ? NULL : - new FastMKS(*other.linear)), - polynomial(other.polynomial == NULL ? NULL : - new FastMKS(*other.polynomial)), - cosine(other.cosine == NULL ? NULL : - new FastMKS(*other.cosine)), - gaussian(other.gaussian == NULL ? NULL : - new FastMKS(*other.gaussian)), - epan(other.epan == NULL ? NULL : - new FastMKS(*other.epan)), - triangular(other.triangular == NULL ? NULL : - new FastMKS(*other.triangular)), - hyptan(other.hyptan == NULL ? NULL : - new FastMKS(*other.hyptan)) -{ - // Nothing to do. -} - -FastMKSModel::FastMKSModel(FastMKSModel&& other) : - kernelType(other.kernelType), - linear(other.linear), - polynomial(other.polynomial), - cosine(other.cosine), - gaussian(other.gaussian), - epan(other.epan), - triangular(other.triangular), - hyptan(other.hyptan) -{ - // Clear other object. - other.kernelType = KernelTypes::LINEAR_KERNEL; - other.linear = NULL; - other.polynomial = NULL; - other.cosine = NULL; - other.gaussian = NULL; - other.epan = NULL; - other.triangular = NULL; - other.hyptan = NULL; -} - -FastMKSModel& FastMKSModel::operator=(const FastMKSModel& other) -{ - if (this != &other) - { - // Clear memory. - delete linear; - delete polynomial; - delete cosine; - delete gaussian; - delete epan; - delete triangular; - delete hyptan; - - // Set pointers to null. - linear = NULL; - polynomial = NULL; - cosine = NULL; - gaussian = NULL; - epan = NULL; - triangular = NULL; - hyptan = NULL; - - kernelType = other.kernelType; - if (other.linear) - linear = new FastMKS(*other.linear); - if (other.polynomial) - polynomial = new FastMKS(*other.polynomial); - if (other.cosine) - cosine = new FastMKS(*other.cosine); - if (other.gaussian) - gaussian = new FastMKS(*other.gaussian); - if (other.epan) - epan = new FastMKS(*other.epan); - if (other.triangular) - triangular = new FastMKS(*other.triangular); - if (other.hyptan) - hyptan = new FastMKS(*other.hyptan); - } - return *this; -} - -FastMKSModel& FastMKSModel::operator=(FastMKSModel&& other) -{ - if (this != &other) - { - kernelType = other.kernelType; - linear = other.linear; - polynomial = other.polynomial; - cosine = other.cosine; - gaussian = other.gaussian; - epan = other.epan; - triangular = other.triangular; - hyptan = other.hyptan; - - // Clear other object. - other.kernelType = KernelTypes::LINEAR_KERNEL; - other.linear = nullptr; - other.polynomial = nullptr; - other.cosine = nullptr; - other.gaussian = nullptr; - other.epan = nullptr; - other.triangular = nullptr; - other.hyptan = nullptr; - } - return *this; -} - -FastMKSModel::~FastMKSModel() -{ - // Clean memory. - if (linear) - delete linear; - if (polynomial) - delete polynomial; - if (cosine) - delete cosine; - if (gaussian) - delete gaussian; - if (epan) - delete epan; - if (triangular) - delete triangular; - if (hyptan) - delete hyptan; -} - -bool FastMKSModel::Naive() const -{ - switch (kernelType) - { - case LINEAR_KERNEL: - return linear->Naive(); - case POLYNOMIAL_KERNEL: - return polynomial->Naive(); - case COSINE_DISTANCE: - return cosine->Naive(); - case GAUSSIAN_KERNEL: - return gaussian->Naive(); - case EPANECHNIKOV_KERNEL: - return epan->Naive(); - case TRIANGULAR_KERNEL: - return triangular->Naive(); - case HYPTAN_KERNEL: - return hyptan->Naive(); - } - - throw std::runtime_error("invalid model type"); -} - -bool& FastMKSModel::Naive() -{ - switch (kernelType) - { - case LINEAR_KERNEL: - return linear->Naive(); - case POLYNOMIAL_KERNEL: - return polynomial->Naive(); - case COSINE_DISTANCE: - return cosine->Naive(); - case GAUSSIAN_KERNEL: - return gaussian->Naive(); - case EPANECHNIKOV_KERNEL: - return epan->Naive(); - case TRIANGULAR_KERNEL: - return triangular->Naive(); - case HYPTAN_KERNEL: - return hyptan->Naive(); - } - - throw std::runtime_error("invalid model type"); -} - -bool FastMKSModel::SingleMode() const -{ - switch (kernelType) - { - case LINEAR_KERNEL: - return linear->SingleMode(); - case POLYNOMIAL_KERNEL: - return polynomial->SingleMode(); - case COSINE_DISTANCE: - return cosine->SingleMode(); - case GAUSSIAN_KERNEL: - return gaussian->SingleMode(); - case EPANECHNIKOV_KERNEL: - return epan->SingleMode(); - case TRIANGULAR_KERNEL: - return triangular->SingleMode(); - case HYPTAN_KERNEL: - return hyptan->SingleMode(); - } - - throw std::runtime_error("invalid model type"); -} - -bool& FastMKSModel::SingleMode() -{ - switch (kernelType) - { - case LINEAR_KERNEL: - return linear->SingleMode(); - case POLYNOMIAL_KERNEL: - return polynomial->SingleMode(); - case COSINE_DISTANCE: - return cosine->SingleMode(); - case GAUSSIAN_KERNEL: - return gaussian->SingleMode(); - case EPANECHNIKOV_KERNEL: - return epan->SingleMode(); - case TRIANGULAR_KERNEL: - return triangular->SingleMode(); - case HYPTAN_KERNEL: - return hyptan->SingleMode(); - } - - throw std::runtime_error("invalid model type"); -} - -void FastMKSModel::Search(util::Timers& timers, - const arma::mat& querySet, - const size_t k, - arma::Mat& indices, - arma::mat& kernels, - const double base) -{ - switch (kernelType) - { - case LINEAR_KERNEL: - Search(timers, *linear, querySet, k, indices, kernels, base); - break; - case POLYNOMIAL_KERNEL: - Search(timers, *polynomial, querySet, k, indices, kernels, base); - break; - case COSINE_DISTANCE: - Search(timers, *cosine, querySet, k, indices, kernels, base); - break; - case GAUSSIAN_KERNEL: - Search(timers, *gaussian, querySet, k, indices, kernels, base); - break; - case EPANECHNIKOV_KERNEL: - Search(timers, *epan, querySet, k, indices, kernels, base); - break; - case TRIANGULAR_KERNEL: - Search(timers, *triangular, querySet, k, indices, kernels, base); - break; - case HYPTAN_KERNEL: - Search(timers, *hyptan, querySet, k, indices, kernels, base); - break; - default: - throw std::runtime_error("invalid model type"); - } -} - -void FastMKSModel::Search(util::Timers& timers, - const size_t k, - arma::Mat& indices, - arma::mat& kernels) -{ - timers.Start("computing_products"); - switch (kernelType) - { - case LINEAR_KERNEL: - linear->Search(k, indices, kernels); - break; - case POLYNOMIAL_KERNEL: - polynomial->Search(k, indices, kernels); - break; - case COSINE_DISTANCE: - cosine->Search(k, indices, kernels); - break; - case GAUSSIAN_KERNEL: - gaussian->Search(k, indices, kernels); - break; - case EPANECHNIKOV_KERNEL: - epan->Search(k, indices, kernels); - break; - case TRIANGULAR_KERNEL: - triangular->Search(k, indices, kernels); - break; - case HYPTAN_KERNEL: - hyptan->Search(k, indices, kernels); - break; - default: - throw std::invalid_argument("invalid model type"); - } - timers.Stop("computing_products"); -} diff --git a/src/mlpack/methods/fastmks/fastmks_model_impl.hpp b/src/mlpack/methods/fastmks/fastmks_model_impl.hpp index 1713832da6..eec9e56b81 100644 --- a/src/mlpack/methods/fastmks/fastmks_model_impl.hpp +++ b/src/mlpack/methods/fastmks/fastmks_model_impl.hpp @@ -17,6 +17,238 @@ namespace mlpack { namespace fastmks { +inline FastMKSModel::FastMKSModel(const int kernelType) : + kernelType(kernelType), + linear(NULL), + polynomial(NULL), + cosine(NULL), + gaussian(NULL), + epan(NULL), + triangular(NULL), + hyptan(NULL) +{ + // Nothing to do. +} + +inline FastMKSModel::FastMKSModel(const FastMKSModel& other) : + kernelType(other.kernelType), + linear(other.linear == NULL ? NULL : + new FastMKS(*other.linear)), + polynomial(other.polynomial == NULL ? NULL : + new FastMKS(*other.polynomial)), + cosine(other.cosine == NULL ? NULL : + new FastMKS(*other.cosine)), + gaussian(other.gaussian == NULL ? NULL : + new FastMKS(*other.gaussian)), + epan(other.epan == NULL ? NULL : + new FastMKS(*other.epan)), + triangular(other.triangular == NULL ? NULL : + new FastMKS(*other.triangular)), + hyptan(other.hyptan == NULL ? NULL : + new FastMKS(*other.hyptan)) +{ + // Nothing to do. +} + +inline FastMKSModel::FastMKSModel(FastMKSModel&& other) : + kernelType(other.kernelType), + linear(other.linear), + polynomial(other.polynomial), + cosine(other.cosine), + gaussian(other.gaussian), + epan(other.epan), + triangular(other.triangular), + hyptan(other.hyptan) +{ + // Clear other object. + other.kernelType = KernelTypes::LINEAR_KERNEL; + other.linear = NULL; + other.polynomial = NULL; + other.cosine = NULL; + other.gaussian = NULL; + other.epan = NULL; + other.triangular = NULL; + other.hyptan = NULL; +} + +inline FastMKSModel& FastMKSModel::operator=(const FastMKSModel& other) +{ + if (this != &other) + { + // Clear memory. + delete linear; + delete polynomial; + delete cosine; + delete gaussian; + delete epan; + delete triangular; + delete hyptan; + + // Set pointers to null. + linear = NULL; + polynomial = NULL; + cosine = NULL; + gaussian = NULL; + epan = NULL; + triangular = NULL; + hyptan = NULL; + + kernelType = other.kernelType; + if (other.linear) + linear = new FastMKS(*other.linear); + if (other.polynomial) + polynomial = new FastMKS(*other.polynomial); + if (other.cosine) + cosine = new FastMKS(*other.cosine); + if (other.gaussian) + gaussian = new FastMKS(*other.gaussian); + if (other.epan) + epan = new FastMKS(*other.epan); + if (other.triangular) + triangular = new FastMKS(*other.triangular); + if (other.hyptan) + hyptan = new FastMKS(*other.hyptan); + } + return *this; +} + +inline FastMKSModel& FastMKSModel::operator=(FastMKSModel&& other) +{ + if (this != &other) + { + kernelType = other.kernelType; + linear = other.linear; + polynomial = other.polynomial; + cosine = other.cosine; + gaussian = other.gaussian; + epan = other.epan; + triangular = other.triangular; + hyptan = other.hyptan; + + // Clear other object. + other.kernelType = KernelTypes::LINEAR_KERNEL; + other.linear = nullptr; + other.polynomial = nullptr; + other.cosine = nullptr; + other.gaussian = nullptr; + other.epan = nullptr; + other.triangular = nullptr; + other.hyptan = nullptr; + } + return *this; +} + +inline FastMKSModel::~FastMKSModel() +{ + // Clean memory. + if (linear) + delete linear; + if (polynomial) + delete polynomial; + if (cosine) + delete cosine; + if (gaussian) + delete gaussian; + if (epan) + delete epan; + if (triangular) + delete triangular; + if (hyptan) + delete hyptan; +} + +inline bool FastMKSModel::Naive() const +{ + switch (kernelType) + { + case LINEAR_KERNEL: + return linear->Naive(); + case POLYNOMIAL_KERNEL: + return polynomial->Naive(); + case COSINE_DISTANCE: + return cosine->Naive(); + case GAUSSIAN_KERNEL: + return gaussian->Naive(); + case EPANECHNIKOV_KERNEL: + return epan->Naive(); + case TRIANGULAR_KERNEL: + return triangular->Naive(); + case HYPTAN_KERNEL: + return hyptan->Naive(); + } + + throw std::runtime_error("invalid model type"); +} + +inline bool& FastMKSModel::Naive() +{ + switch (kernelType) + { + case LINEAR_KERNEL: + return linear->Naive(); + case POLYNOMIAL_KERNEL: + return polynomial->Naive(); + case COSINE_DISTANCE: + return cosine->Naive(); + case GAUSSIAN_KERNEL: + return gaussian->Naive(); + case EPANECHNIKOV_KERNEL: + return epan->Naive(); + case TRIANGULAR_KERNEL: + return triangular->Naive(); + case HYPTAN_KERNEL: + return hyptan->Naive(); + } + + throw std::runtime_error("invalid model type"); +} + +inline bool FastMKSModel::SingleMode() const +{ + switch (kernelType) + { + case LINEAR_KERNEL: + return linear->SingleMode(); + case POLYNOMIAL_KERNEL: + return polynomial->SingleMode(); + case COSINE_DISTANCE: + return cosine->SingleMode(); + case GAUSSIAN_KERNEL: + return gaussian->SingleMode(); + case EPANECHNIKOV_KERNEL: + return epan->SingleMode(); + case TRIANGULAR_KERNEL: + return triangular->SingleMode(); + case HYPTAN_KERNEL: + return hyptan->SingleMode(); + } + + throw std::runtime_error("invalid model type"); +} + +inline bool& FastMKSModel::SingleMode() +{ + switch (kernelType) + { + case LINEAR_KERNEL: + return linear->SingleMode(); + case POLYNOMIAL_KERNEL: + return polynomial->SingleMode(); + case COSINE_DISTANCE: + return cosine->SingleMode(); + case GAUSSIAN_KERNEL: + return gaussian->SingleMode(); + case EPANECHNIKOV_KERNEL: + return epan->SingleMode(); + case TRIANGULAR_KERNEL: + return triangular->SingleMode(); + case HYPTAN_KERNEL: + return hyptan->SingleMode(); + } + + throw std::runtime_error("invalid model type"); +} + //! This is called when the KernelType is the same as the model. template void BuildFastMKSModel(util::Timers& timers, @@ -229,6 +461,78 @@ void FastMKSModel::Search(util::Timers& timers, } } +inline void FastMKSModel::Search( + util::Timers& timers, + const arma::mat& querySet, + const size_t k, + arma::Mat& indices, + arma::mat& kernels, + const double base) +{ + switch (kernelType) + { + case LINEAR_KERNEL: + Search(timers, *linear, querySet, k, indices, kernels, base); + break; + case POLYNOMIAL_KERNEL: + Search(timers, *polynomial, querySet, k, indices, kernels, base); + break; + case COSINE_DISTANCE: + Search(timers, *cosine, querySet, k, indices, kernels, base); + break; + case GAUSSIAN_KERNEL: + Search(timers, *gaussian, querySet, k, indices, kernels, base); + break; + case EPANECHNIKOV_KERNEL: + Search(timers, *epan, querySet, k, indices, kernels, base); + break; + case TRIANGULAR_KERNEL: + Search(timers, *triangular, querySet, k, indices, kernels, base); + break; + case HYPTAN_KERNEL: + Search(timers, *hyptan, querySet, k, indices, kernels, base); + break; + default: + throw std::runtime_error("invalid model type"); + } +} + +inline void FastMKSModel::Search( + util::Timers& timers, + const size_t k, + arma::Mat& indices, + arma::mat& kernels) +{ + timers.Start("computing_products"); + switch (kernelType) + { + case LINEAR_KERNEL: + linear->Search(k, indices, kernels); + break; + case POLYNOMIAL_KERNEL: + polynomial->Search(k, indices, kernels); + break; + case COSINE_DISTANCE: + cosine->Search(k, indices, kernels); + break; + case GAUSSIAN_KERNEL: + gaussian->Search(k, indices, kernels); + break; + case EPANECHNIKOV_KERNEL: + epan->Search(k, indices, kernels); + break; + case TRIANGULAR_KERNEL: + triangular->Search(k, indices, kernels); + break; + case HYPTAN_KERNEL: + hyptan->Search(k, indices, kernels); + break; + default: + throw std::invalid_argument("invalid model type"); + } + timers.Stop("computing_products"); +} + } // namespace fastmks } // namespace mlpack diff --git a/src/mlpack/methods/gmm/CMakeLists.txt b/src/mlpack/methods/gmm/CMakeLists.txt index abbfb90668..c351d45607 100644 --- a/src/mlpack/methods/gmm/CMakeLists.txt +++ b/src/mlpack/methods/gmm/CMakeLists.txt @@ -2,10 +2,8 @@ # Anything not in this list will not be compiled into mlpack. set(SOURCES gmm.hpp - gmm.cpp gmm_impl.hpp diagonal_gmm.hpp - diagonal_gmm.cpp diagonal_gmm_impl.hpp em_fit.hpp em_fit_impl.hpp diff --git a/src/mlpack/methods/gmm/diagonal_gmm.cpp b/src/mlpack/methods/gmm/diagonal_gmm.cpp deleted file mode 100644 index ab7c30e1ac..0000000000 --- a/src/mlpack/methods/gmm/diagonal_gmm.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/** - * @file methods/gmm/diagonal_gmm.cpp - * @author Kim SangYeon - * - * Implementation of template-based GMM methods. - * - * 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. - */ - -#include "diagonal_gmm.hpp" -#include - -namespace mlpack { -namespace gmm { - -/** - * Create a DiagonalGMM with the given number of Gaussians, each of which have - * the specified dimensionality. The means and covariances will be set to 0. - * - * @param gaussians Number of Gaussians in this GMM. - * @param dimensionality Dimensionality of each Gaussian. - */ -DiagonalGMM::DiagonalGMM(const size_t gaussians, const size_t dimensionality) : - gaussians(gaussians), - dimensionality(dimensionality), - dists(gaussians, - distribution::DiagonalGaussianDistribution(dimensionality)), - weights(gaussians) -{ - // Set equal weights. Technically this model is still valid, but only barely. - weights.fill(1.0 / gaussians); -} - -// Copy constructor for when the other GMM uses the same fitting type. -DiagonalGMM::DiagonalGMM(const DiagonalGMM& other) : - gaussians(other.Gaussians()), - dimensionality(other.dimensionality), - dists(other.dists), - weights(other.weights) { /* Nothing to do. */ } - -DiagonalGMM& DiagonalGMM::operator=(const DiagonalGMM& other) -{ - gaussians = other.gaussians; - dimensionality = other.dimensionality; - dists = other.dists; - weights = other.weights; - - return *this; -} - -/** - * Return the log probability of the given observation being from this GMM. - */ -double DiagonalGMM::LogProbability(const arma::vec& observation) const -{ - // Sum the probability for each Gaussian in our mixture (and we have to - // multiply by the prior for each Gaussian too). - double sum = -std::numeric_limits::infinity(); - for (size_t i = 0; i < gaussians; ++i) - { - sum = math::LogAdd(sum, log(weights[i]) + - dists[i].LogProbability(observation)); - } - return sum; -} - -/** - * Return the log probability of the given observation GMM matrix. - * - * @param observation Observation matrix to compute log-probabilty. - * @param logProbs Stores the value of log-probability for input. - */ -void DiagonalGMM::LogProbability(const arma::mat& observation, - arma::vec& logProbs) const -{ - // Sum the probability for each Gaussian in our mixture (and we have to - // multiply by the prior for each Gaussian too). - logProbs.set_size(observation.n_cols); - - // Store log-probability value in a matrix. - arma::mat logProb(observation.n_cols, gaussians); - - // Assign value to the matrix. - for (size_t i = 0; i < gaussians; i++) - { - arma::vec temp(logProb.colptr(i), observation.n_cols, false, true); - dists[i].LogProbability(observation, temp); - } - - // Save log(weights) as a vector. - arma::vec logWeights = arma::log(weights); - - // Compute log-probability. - logProb += repmat(logWeights.t(), logProb.n_rows, 1); - math::LogSumExp(logProb, logProbs); -} - -/** - * Return the probability of the given observation being from this GMM. - */ -double DiagonalGMM::Probability(const arma::vec& observation) const -{ - return exp(LogProbability(observation)); -} - -/** - * Return the probability of the given observation GMM matrix. - * - * @param observation Observation matrix to compute probabilty. - * @param probs Stores the value of probability for observation. - */ -void DiagonalGMM::Probability(const arma::mat& observation, - arma::vec& probs) const -{ - LogProbability(observation, probs); - probs = exp(probs); -} - - -/** - * Return the log probability of the given observation being from the given - * component in the mixture. - */ -double DiagonalGMM::LogProbability(const arma::vec& observation, - const size_t component) const -{ - // We are only considering one Gaussian component -- so we only need to call - // Probability() once. We do consider the prior probability! - return log(weights[component]) + - dists[component].LogProbability(observation); -} - -/** - * Return the probability of the given observation being from the given - * component in the mixture. - */ -double DiagonalGMM::Probability(const arma::vec& observation, - const size_t component) const -{ - return exp(LogProbability(observation, component)); -} - -/** - * Return a randomly generated observation according to the probability - * distribution defined by this object. - */ -arma::vec DiagonalGMM::Random() const -{ - // Determine which Gaussian it will be coming from. - double gaussRand = math::Random(); - size_t gaussian = 0; - - double sumProb = 0; - for (size_t g = 0; g < gaussians; g++) - { - sumProb += weights(g); - if (gaussRand <= sumProb) - { - gaussian = g; - break; - } - } - - return arma::sqrt(dists[gaussian].Covariance()) % - arma::randn(dimensionality) + dists[gaussian].Mean(); -} - -/** - * Classify the given observations as being from an individual component in - * this GMM. - */ -void DiagonalGMM::Classify(const arma::mat& observations, - arma::Row& labels) const -{ - // This is not the best way to do this! - - // We should not have to fill this with values, because each one should be - // overwritten. - labels.set_size(observations.n_cols); - for (size_t i = 0; i < observations.n_cols; ++i) - { - // Find maximum probability component. - double probability = 0; - for (size_t j = 0; j < gaussians; ++j) - { - double newProb = Probability(observations.unsafe_col(i), j); - if (newProb >= probability) - { - probability = newProb; - labels[i] = j; - } - } - } -} - -/** - * Get the log-likelihood of this data's fit to the model. - */ -double DiagonalGMM::LogLikelihood( - const arma::mat& observations, - const std::vector& dists, - const arma::vec& weights) const -{ - double logLikelihood = 0; - arma::vec phis; - arma::mat likelihoods(gaussians, observations.n_cols); - - for (size_t i = 0; i < gaussians; ++i) - { - dists[i].Probability(observations, phis); - likelihoods.row(i) = weights(i) * trans(phis); - } - - // Now sum over every point. - for (size_t j = 0; j < observations.n_cols; ++j) - { - if (accu(likelihoods.col(j)) == 0) - Log::Info << "Likelihood of point " << j << " is 0! It is probably an " - << "outlier." << std::endl; - logLikelihood += log(accu(likelihoods.col(j))); - } - - return logLikelihood; -} - -} // namespace gmm -} // namespace mlpack diff --git a/src/mlpack/methods/gmm/diagonal_gmm.hpp b/src/mlpack/methods/gmm/diagonal_gmm.hpp index ef8fe9445a..937b4662b5 100644 --- a/src/mlpack/methods/gmm/diagonal_gmm.hpp +++ b/src/mlpack/methods/gmm/diagonal_gmm.hpp @@ -16,6 +16,7 @@ #include #include +#include // This is the default fitting method class. #include "em_fit.hpp" diff --git a/src/mlpack/methods/gmm/diagonal_gmm_impl.hpp b/src/mlpack/methods/gmm/diagonal_gmm_impl.hpp index 61a172c654..fc33988d4f 100644 --- a/src/mlpack/methods/gmm/diagonal_gmm_impl.hpp +++ b/src/mlpack/methods/gmm/diagonal_gmm_impl.hpp @@ -101,6 +101,218 @@ double DiagonalGMM::Train(const arma::mat& observations, return bestLikelihood; } +/** + * Create a DiagonalGMM with the given number of Gaussians, each of which have + * the specified dimensionality. The means and covariances will be set to 0. + * + * @param gaussians Number of Gaussians in this GMM. + * @param dimensionality Dimensionality of each Gaussian. + */ +inline DiagonalGMM::DiagonalGMM( + const size_t gaussians, + const size_t dimensionality) : + gaussians(gaussians), + dimensionality(dimensionality), + dists(gaussians, + distribution::DiagonalGaussianDistribution(dimensionality)), + weights(gaussians) +{ + // Set equal weights. Technically this model is still valid, but only barely. + weights.fill(1.0 / gaussians); +} + +// Copy constructor for when the other GMM uses the same fitting type. +inline DiagonalGMM::DiagonalGMM(const DiagonalGMM& other) : + gaussians(other.Gaussians()), + dimensionality(other.dimensionality), + dists(other.dists), + weights(other.weights) { /* Nothing to do. */ } + +inline DiagonalGMM& DiagonalGMM::operator=(const DiagonalGMM& other) +{ + gaussians = other.gaussians; + dimensionality = other.dimensionality; + dists = other.dists; + weights = other.weights; + + return *this; +} + +/** + * Return the log probability of the given observation being from this GMM. + */ +inline double DiagonalGMM::LogProbability(const arma::vec& observation) const +{ + // Sum the probability for each Gaussian in our mixture (and we have to + // multiply by the prior for each Gaussian too). + double sum = -std::numeric_limits::infinity(); + for (size_t i = 0; i < gaussians; ++i) + { + sum = math::LogAdd(sum, log(weights[i]) + + dists[i].LogProbability(observation)); + } + return sum; +} + +/** + * Return the log probability of the given observation GMM matrix. + * + * @param observation Observation matrix to compute log-probabilty. + * @param logProbs Stores the value of log-probability for input. + */ +inline void DiagonalGMM::LogProbability(const arma::mat& observation, + arma::vec& logProbs) const +{ + // Sum the probability for each Gaussian in our mixture (and we have to + // multiply by the prior for each Gaussian too). + logProbs.set_size(observation.n_cols); + + // Store log-probability value in a matrix. + arma::mat logProb(observation.n_cols, gaussians); + + // Assign value to the matrix. + for (size_t i = 0; i < gaussians; i++) + { + arma::vec temp(logProb.colptr(i), observation.n_cols, false, true); + dists[i].LogProbability(observation, temp); + } + + // Save log(weights) as a vector. + arma::vec logWeights = arma::log(weights); + + // Compute log-probability. + logProb += repmat(logWeights.t(), logProb.n_rows, 1); + math::LogSumExp(logProb, logProbs); +} + +/** + * Return the probability of the given observation being from this GMM. + */ +inline double DiagonalGMM::Probability(const arma::vec& observation) const +{ + return exp(LogProbability(observation)); +} + +/** + * Return the probability of the given observation GMM matrix. + * + * @param observation Observation matrix to compute probabilty. + * @param probs Stores the value of probability for observation. + */ +inline void DiagonalGMM::Probability(const arma::mat& observation, + arma::vec& probs) const +{ + LogProbability(observation, probs); + probs = exp(probs); +} + + +/** + * Return the log probability of the given observation being from the given + * component in the mixture. + */ +inline double DiagonalGMM::LogProbability(const arma::vec& observation, + const size_t component) const +{ + // We are only considering one Gaussian component -- so we only need to call + // Probability() once. We do consider the prior probability! + return log(weights[component]) + + dists[component].LogProbability(observation); +} + +/** + * Return the probability of the given observation being from the given + * component in the mixture. + */ +inline double DiagonalGMM::Probability(const arma::vec& observation, + const size_t component) const +{ + return exp(LogProbability(observation, component)); +} + +/** + * Return a randomly generated observation according to the probability + * distribution defined by this object. + */ +inline arma::vec DiagonalGMM::Random() const +{ + // Determine which Gaussian it will be coming from. + double gaussRand = math::Random(); + size_t gaussian = 0; + + double sumProb = 0; + for (size_t g = 0; g < gaussians; g++) + { + sumProb += weights(g); + if (gaussRand <= sumProb) + { + gaussian = g; + break; + } + } + + return arma::sqrt(dists[gaussian].Covariance()) % + arma::randn(dimensionality) + dists[gaussian].Mean(); +} + +/** + * Classify the given observations as being from an individual component in + * this GMM. + */ +inline void DiagonalGMM::Classify(const arma::mat& observations, + arma::Row& labels) const +{ + // This is not the best way to do this! + + // We should not have to fill this with values, because each one should be + // overwritten. + labels.set_size(observations.n_cols); + for (size_t i = 0; i < observations.n_cols; ++i) + { + // Find maximum probability component. + double probability = 0; + for (size_t j = 0; j < gaussians; ++j) + { + double newProb = Probability(observations.unsafe_col(i), j); + if (newProb >= probability) + { + probability = newProb; + labels[i] = j; + } + } + } +} + +/** + * Get the log-likelihood of this data's fit to the model. + */ +inline double DiagonalGMM::LogLikelihood( + const arma::mat& observations, + const std::vector& dists, + const arma::vec& weights) const +{ + double logLikelihood = 0; + arma::vec phis; + arma::mat likelihoods(gaussians, observations.n_cols); + + for (size_t i = 0; i < gaussians; ++i) + { + dists[i].Probability(observations, phis); + likelihoods.row(i) = weights(i) * trans(phis); + } + + // Now sum over every point. + for (size_t j = 0; j < observations.n_cols; ++j) + { + if (accu(likelihoods.col(j)) == 0) + Log::Info << "Likelihood of point " << j << " is 0! It is probably an " + << "outlier." << std::endl; + logLikelihood += log(accu(likelihoods.col(j))); + } + + return logLikelihood; +} + /** * Fit the DiagonalGMM to the given observations, each of which has a certain * probability of being from this distribution. diff --git a/src/mlpack/methods/gmm/gmm.cpp b/src/mlpack/methods/gmm/gmm.cpp deleted file mode 100644 index 42603d45c7..0000000000 --- a/src/mlpack/methods/gmm/gmm.cpp +++ /dev/null @@ -1,247 +0,0 @@ -/** - * @file methods/gmm/gmm.cpp - * @author Parikshit Ram (pram@cc.gatech.edu) - * @author Ryan Curtin - * @author Michael Fox - * - * Implementation of template-based GMM methods. - * - * 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. - */ -#include "gmm.hpp" -#include - -namespace mlpack { -namespace gmm { - -/** - * Create a GMM with the given number of Gaussians, each of which have the - * specified dimensionality. The means and covariances will be set to 0. - * - * @param gaussians Number of Gaussians in this GMM. - * @param dimensionality Dimensionality of each Gaussian. - */ -GMM::GMM(const size_t gaussians, const size_t dimensionality) : - gaussians(gaussians), - dimensionality(dimensionality), - dists(gaussians, distribution::GaussianDistribution(dimensionality)), - weights(gaussians) -{ - // Set equal weights. Technically this model is still valid, but only barely. - weights.fill(1.0 / gaussians); -} - -// Copy constructor for when the other GMM uses the same fitting type. -GMM::GMM(const GMM& other) : - gaussians(other.Gaussians()), - dimensionality(other.dimensionality), - dists(other.dists), - weights(other.weights) { /* Nothing to do. */ } - -GMM& GMM::operator=(const GMM& other) -{ - gaussians = other.gaussians; - dimensionality = other.dimensionality; - dists = other.dists; - weights = other.weights; - - return *this; -} - -/** - * Return the log probability of the given observation being from this GMM. - * - * @param observation Observation vector to compute log-probabilty. - */ -double GMM::LogProbability(const arma::vec& observation) const -{ - // Sum the probability for each Gaussian in our mixture (and we have to - // multiply by the prior for each Gaussian too). - double sum = -std::numeric_limits::infinity(); - for (size_t i = 0; i < gaussians; ++i) - sum = math::LogAdd(sum, log(weights[i]) + - dists[i].LogProbability(observation)); - - return sum; -} - -/** - * Return the log probability of the given observation GMM matrix. - * - * @param observation Observation matrix to compute log-probabilty. - * @param logProbs Stores the value of log-probability for Observation. - */ -void GMM::LogProbability(const arma::mat& observation, - arma::vec& logProbs) const -{ - // Sum the probability for each Gaussian in our mixture (and we have to - // multiply by the prior for each Gaussian too). - logProbs.set_size(observation.n_cols); - - // Store log-probability value in a matrix. - arma::mat logProb(observation.n_cols, gaussians); - - // Assign value to the matrix. - for (size_t i = 0; i < gaussians; i++) - { - arma::vec temp(logProb.colptr(i), observation.n_cols, false, true); - dists[i].LogProbability(observation, temp); - } - - // Save log(weights) as a vector. - arma::vec logWeights = arma::log(weights); - - // Compute log-probability. - logProb += repmat(logWeights.t(), logProb.n_rows, 1); - math::LogSumExp(logProb, logProbs); -} - -/** - * Return the probability of the given observation being from this GMM. - * - * @param observation Observation vector to compute probabilty. - */ -double GMM::Probability(const arma::vec& observation) const -{ - return exp(LogProbability(observation)); -} - -/** - * Return the probability of the given observation GMM matrix. - * - * @param observation Observation matrix to compute probabilty. - * @param probs Stores the value of probability for x. - */ -void GMM::Probability(const arma::mat& observation, - arma::vec& probs) const -{ - LogProbability(observation, probs); - probs = exp(probs); -} - - -/** - * Return the log probability of the given observation being from the given - * component in the mixture. - * - * @param observation Observation vector to compute log-probabilty. - * @param component Calculate the log-probability for given observation vector. - */ -double GMM::LogProbability(const arma::vec& observation, - const size_t component) const -{ - // We are only considering one Gaussian component -- so we only need to call - // Probability() once. We do consider the prior probability! - return log(weights[component]) + dists[component].LogProbability(observation); -} - -/** - * Return the probability of the given observation being from the given - * component in the mixture. - * - * @param observation Observation matrix to compute probabilty. - * @param component Calculate the probability for given component. - */ -double GMM::Probability(const arma::vec& observation, - const size_t component) const -{ - return exp(LogProbability(observation, component)); -} - -/** - * Return a randomly generated observation according to the probability - * distribution defined by this object. - */ -arma::vec GMM::Random() const -{ - // Determine which Gaussian it will be coming from. - double gaussRand = math::Random(); - size_t gaussian = 0; - - double sumProb = 0; - for (size_t g = 0; g < gaussians; g++) - { - sumProb += weights(g); - if (gaussRand <= sumProb) - { - gaussian = g; - break; - } - } - - arma::mat cholDecomp; - if (!arma::chol(cholDecomp, dists[gaussian].Covariance())) - { - Log::Fatal << "Cholesky decomposition failed." << std::endl; - } - return trans(cholDecomp) * - arma::randn(dimensionality) + dists[gaussian].Mean(); -} - -/** - * Classify the given observations as being from an individual component in this - * GMM. - * - * @param observation Observation matrix for classification. - * @param labels Save the labels for the given observation matrix. - */ -void GMM::Classify(const arma::mat& observations, - arma::Row& labels) const -{ - // This is not the best way to do this! - - // We should not have to fill this with values, because each one should be - // overwritten. - labels.set_size(observations.n_cols); - for (size_t i = 0; i < observations.n_cols; ++i) - { - // Find maximum probability component. - double probability = -std::numeric_limits::infinity(); - for (size_t j = 0; j < gaussians; ++j) - { - // We have to use LogProbability() otherwise Probability() would overflow - // easily. - double newProb = LogProbability(observations.unsafe_col(i), j); - if (newProb >= probability) - { - probability = newProb; - labels[i] = j; - } - } - } -} - -/** - * Get the log-likelihood of this data's fit to the model. - * - * @param data Data matrix to compute log-likelihood. - * @parma distsL Vector of Gaussian distribution. - * @param weightsL Vector of weights for computing likelihoods. - */ -double GMM::LogLikelihood( - const arma::mat& data, - const std::vector& distsL, - const arma::vec& weightsL) const -{ - double loglikelihood = 0; - arma::vec logPhis; - arma::mat logLikelihoods(gaussians, data.n_cols); - - // It has to be LogProbability() otherwise Probability() would overflow easily - for (size_t i = 0; i < gaussians; ++i) - { - distsL[i].LogProbability(data, logPhis); - logLikelihoods.row(i) = log(weightsL(i)) + trans(logPhis); - } - - // Now sum over every point. - for (size_t j = 0; j < data.n_cols; ++j) - loglikelihood += mlpack::math::AccuLog(logLikelihoods.col(j)); - return loglikelihood; -} - -} // namespace gmm -} // namespace mlpack diff --git a/src/mlpack/methods/gmm/gmm.hpp b/src/mlpack/methods/gmm/gmm.hpp index b8436ce995..05d2b61a32 100644 --- a/src/mlpack/methods/gmm/gmm.hpp +++ b/src/mlpack/methods/gmm/gmm.hpp @@ -10,14 +10,16 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ -#ifndef MLPACK_METHODS_MOG_MOG_EM_HPP -#define MLPACK_METHODS_MOG_MOG_EM_HPP +#ifndef MLPACK_METHODS_GMM_GMM_HPP +#define MLPACK_METHODS_GMM_GMM_HPP #include // This is the default fitting method class. #include "em_fit.hpp" +#include + namespace mlpack { namespace gmm /** Gaussian Mixture Models. */ { diff --git a/src/mlpack/methods/gmm/gmm_impl.hpp b/src/mlpack/methods/gmm/gmm_impl.hpp index 876e2a068a..b332284560 100644 --- a/src/mlpack/methods/gmm/gmm_impl.hpp +++ b/src/mlpack/methods/gmm/gmm_impl.hpp @@ -20,6 +20,232 @@ namespace mlpack { namespace gmm { +/** + * Create a GMM with the given number of Gaussians, each of which have the + * specified dimensionality. The means and covariances will be set to 0. + * + * @param gaussians Number of Gaussians in this GMM. + * @param dimensionality Dimensionality of each Gaussian. + */ +inline GMM::GMM(const size_t gaussians, const size_t dimensionality) : + gaussians(gaussians), + dimensionality(dimensionality), + dists(gaussians, distribution::GaussianDistribution(dimensionality)), + weights(gaussians) +{ + // Set equal weights. Technically this model is still valid, but only barely. + weights.fill(1.0 / gaussians); +} + +// Copy constructor for when the other GMM uses the same fitting type. +inline GMM::GMM(const GMM& other) : + gaussians(other.Gaussians()), + dimensionality(other.dimensionality), + dists(other.dists), + weights(other.weights) { /* Nothing to do. */ } + +inline GMM& GMM::operator=(const GMM& other) +{ + gaussians = other.gaussians; + dimensionality = other.dimensionality; + dists = other.dists; + weights = other.weights; + + return *this; +} + +/** + * Return the log probability of the given observation being from this GMM. + * + * @param observation Observation vector to compute log-probabilty. + */ +inline double GMM::LogProbability(const arma::vec& observation) const +{ + // Sum the probability for each Gaussian in our mixture (and we have to + // multiply by the prior for each Gaussian too). + double sum = -std::numeric_limits::infinity(); + for (size_t i = 0; i < gaussians; ++i) + sum = math::LogAdd(sum, log(weights[i]) + + dists[i].LogProbability(observation)); + + return sum; +} + +/** + * Return the log probability of the given observation GMM matrix. + * + * @param observation Observation matrix to compute log-probabilty. + * @param logProbs Stores the value of log-probability for Observation. + */ +inline void GMM::LogProbability(const arma::mat& observation, + arma::vec& logProbs) const +{ + // Sum the probability for each Gaussian in our mixture (and we have to + // multiply by the prior for each Gaussian too). + logProbs.set_size(observation.n_cols); + + // Store log-probability value in a matrix. + arma::mat logProb(observation.n_cols, gaussians); + + // Assign value to the matrix. + for (size_t i = 0; i < gaussians; i++) + { + arma::vec temp(logProb.colptr(i), observation.n_cols, false, true); + dists[i].LogProbability(observation, temp); + } + + // Save log(weights) as a vector. + arma::vec logWeights = arma::log(weights); + + // Compute log-probability. + logProb += repmat(logWeights.t(), logProb.n_rows, 1); + math::LogSumExp(logProb, logProbs); +} + +/** + * Return the probability of the given observation being from this GMM. + * + * @param observation Observation vector to compute probabilty. + */ +inline double GMM::Probability(const arma::vec& observation) const +{ + return exp(LogProbability(observation)); +} + +/** + * Return the probability of the given observation GMM matrix. + * + * @param observation Observation matrix to compute probabilty. + * @param probs Stores the value of probability for x. + */ +inline void GMM::Probability(const arma::mat& observation, + arma::vec& probs) const +{ + LogProbability(observation, probs); + probs = exp(probs); +} + + +/** + * Return the log probability of the given observation being from the given + * component in the mixture. + * + * @param observation Observation vector to compute log-probabilty. + * @param component Calculate the log-probability for given observation vector. + */ +inline double GMM::LogProbability(const arma::vec& observation, + const size_t component) const +{ + // We are only considering one Gaussian component -- so we only need to call + // Probability() once. We do consider the prior probability! + return log(weights[component]) + dists[component].LogProbability(observation); +} + +/** + * Return the probability of the given observation being from the given + * component in the mixture. + * + * @param observation Observation matrix to compute probabilty. + * @param component Calculate the probability for given component. + */ +inline double GMM::Probability(const arma::vec& observation, + const size_t component) const +{ + return exp(LogProbability(observation, component)); +} + +/** + * Return a randomly generated observation according to the probability + * distribution defined by this object. + */ +inline arma::vec GMM::Random() const +{ + // Determine which Gaussian it will be coming from. + double gaussRand = math::Random(); + size_t gaussian = 0; + + double sumProb = 0; + for (size_t g = 0; g < gaussians; g++) + { + sumProb += weights(g); + if (gaussRand <= sumProb) + { + gaussian = g; + break; + } + } + + arma::mat cholDecomp; + if (!arma::chol(cholDecomp, dists[gaussian].Covariance())) + { + Log::Fatal << "Cholesky decomposition failed." << std::endl; + } + return trans(cholDecomp) * + arma::randn(dimensionality) + dists[gaussian].Mean(); +} + +/** + * Classify the given observations as being from an individual component in this + * GMM. + * + * @param observation Observation matrix for classification. + * @param labels Save the labels for the given observation matrix. + */ +inline void GMM::Classify(const arma::mat& observations, + arma::Row& labels) const +{ + // This is not the best way to do this! + + // We should not have to fill this with values, because each one should be + // overwritten. + labels.set_size(observations.n_cols); + for (size_t i = 0; i < observations.n_cols; ++i) + { + // Find maximum probability component. + double probability = -std::numeric_limits::infinity(); + for (size_t j = 0; j < gaussians; ++j) + { + // We have to use LogProbability() otherwise Probability() would overflow + // easily. + double newProb = LogProbability(observations.unsafe_col(i), j); + if (newProb >= probability) + { + probability = newProb; + labels[i] = j; + } + } + } +} + +/** + * Get the log-likelihood of this data's fit to the model. + * + * @param data Data matrix to compute log-likelihood. + * @parma distsL Vector of Gaussian distribution. + * @param weightsL Vector of weights for computing likelihoods. + */ +inline double GMM::LogLikelihood( + const arma::mat& data, + const std::vector& distsL, + const arma::vec& weightsL) const +{ + double loglikelihood = 0; + arma::vec logPhis; + arma::mat logLikelihoods(gaussians, data.n_cols); + + // It has to be LogProbability() otherwise Probability() would overflow easily + for (size_t i = 0; i < gaussians; ++i) + { + distsL[i].LogProbability(data, logPhis); + logLikelihoods.row(i) = log(weightsL(i)) + trans(logPhis); + } + + // Now sum over every point. + for (size_t j = 0; j < data.n_cols; ++j) + loglikelihood += mlpack::math::AccuLog(logLikelihoods.col(j)); + return loglikelihood; +} + /** * Fit the GMM to the given observations. */ diff --git a/src/mlpack/methods/hoeffding_trees/CMakeLists.txt b/src/mlpack/methods/hoeffding_trees/CMakeLists.txt index 69f390f6ea..acf4f47b8a 100644 --- a/src/mlpack/methods/hoeffding_trees/CMakeLists.txt +++ b/src/mlpack/methods/hoeffding_trees/CMakeLists.txt @@ -13,7 +13,7 @@ set(SOURCES hoeffding_tree.hpp hoeffding_tree_impl.hpp hoeffding_tree_model.hpp - hoeffding_tree_model.cpp + hoeffding_tree_model_impl.hpp information_gain.hpp numeric_split_info.hpp typedef.hpp diff --git a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.hpp b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.hpp index f375a622c2..1ae7dee758 100644 --- a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.hpp +++ b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.hpp @@ -15,6 +15,7 @@ #include "hoeffding_tree.hpp" #include "binary_numeric_split.hpp" #include "information_gain.hpp" +#include namespace mlpack { namespace tree { @@ -220,4 +221,7 @@ class HoeffdingTreeModel } // namespace tree } // namespace mlpack +// Include implementation. +#include "hoeffding_tree_model_impl.hpp" + #endif diff --git a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.cpp b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model_impl.hpp similarity index 84% rename from src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.cpp rename to src/mlpack/methods/hoeffding_trees/hoeffding_tree_model_impl.hpp index 2dfe857edf..a02eebec65 100644 --- a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.cpp +++ b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model_impl.hpp @@ -1,5 +1,5 @@ /** - * @param hoeffding_tree_model.cpp + * @file methods/hoeffding_trees/hoeffding_tree_model_impl.hpp * @author Ryan Curtin * * Implementation of the HoeffdingTreeModel class. @@ -9,15 +9,16 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_METHODS_HOEFFDING_TREE_HOEFFDING_TREE_MODEL_IMPL_HPP +#define MLPACK_METHODS_HOEFFDING_TREE_HOEFFDING_TREE_MODEL_IMPL_HPP + #include "hoeffding_tree_model.hpp" -#include - -using namespace mlpack; -using namespace mlpack::tree; +namespace mlpack { +namespace tree { // Constructor. -HoeffdingTreeModel::HoeffdingTreeModel(const TreeType& type) : +inline HoeffdingTreeModel::HoeffdingTreeModel(const TreeType& type) : type(type), giniHoeffdingTree(NULL), giniBinaryTree(NULL), @@ -28,7 +29,7 @@ HoeffdingTreeModel::HoeffdingTreeModel(const TreeType& type) : } // Copy constructor. -HoeffdingTreeModel::HoeffdingTreeModel(const HoeffdingTreeModel& other) : +inline HoeffdingTreeModel::HoeffdingTreeModel(const HoeffdingTreeModel& other) : type(other.type), giniHoeffdingTree(other.giniHoeffdingTree ? new GiniHoeffdingTreeType( *other.giniHoeffdingTree) : NULL), @@ -43,7 +44,7 @@ HoeffdingTreeModel::HoeffdingTreeModel(const HoeffdingTreeModel& other) : } // Move constructor. -HoeffdingTreeModel::HoeffdingTreeModel(HoeffdingTreeModel&& other) : +inline HoeffdingTreeModel::HoeffdingTreeModel(HoeffdingTreeModel&& other) : type(other.type), giniHoeffdingTree(other.giniHoeffdingTree), giniBinaryTree(other.giniBinaryTree), @@ -59,7 +60,7 @@ HoeffdingTreeModel::HoeffdingTreeModel(HoeffdingTreeModel&& other) : } // Copy operator. -HoeffdingTreeModel& HoeffdingTreeModel::operator=( +inline HoeffdingTreeModel& HoeffdingTreeModel::operator=( const HoeffdingTreeModel& other) { if (this != &other) @@ -90,7 +91,8 @@ HoeffdingTreeModel& HoeffdingTreeModel::operator=( } // Move operator. -HoeffdingTreeModel& HoeffdingTreeModel::operator=(HoeffdingTreeModel&& other) +inline HoeffdingTreeModel& HoeffdingTreeModel::operator=( + HoeffdingTreeModel&& other) { if (this != &other) { @@ -117,7 +119,7 @@ HoeffdingTreeModel& HoeffdingTreeModel::operator=(HoeffdingTreeModel&& other) } // Destructor. -HoeffdingTreeModel::~HoeffdingTreeModel() +inline HoeffdingTreeModel::~HoeffdingTreeModel() { delete giniHoeffdingTree; delete giniBinaryTree; @@ -126,7 +128,7 @@ HoeffdingTreeModel::~HoeffdingTreeModel() } // Create the model. -void HoeffdingTreeModel::BuildModel( +inline void HoeffdingTreeModel::BuildModel( const arma::mat& dataset, const data::DatasetInfo& datasetInfo, const arma::Row& labels, @@ -189,9 +191,9 @@ void HoeffdingTreeModel::BuildModel( } // Train the model on one pass of the dataset. -void HoeffdingTreeModel::Train(const arma::mat& dataset, - const arma::Row& labels, - const bool batchTraining) +inline void HoeffdingTreeModel::Train(const arma::mat& dataset, + const arma::Row& labels, + const bool batchTraining) { // Depending on the type, pass through once. switch (type) @@ -215,8 +217,9 @@ void HoeffdingTreeModel::Train(const arma::mat& dataset, } // Classify the given points. -void HoeffdingTreeModel::Classify(const arma::mat& dataset, - arma::Row& predictions) const +inline void HoeffdingTreeModel::Classify(const arma::mat& dataset, + arma::Row& predictions) + const { // Call Classify() with the right model. switch (type) @@ -240,9 +243,10 @@ void HoeffdingTreeModel::Classify(const arma::mat& dataset, } // Classify the given points. -void HoeffdingTreeModel::Classify(const arma::mat& dataset, - arma::Row& predictions, - arma::rowvec& probabilities) const +inline void HoeffdingTreeModel::Classify(const arma::mat& dataset, + arma::Row& predictions, + arma::rowvec& probabilities) + const { // Call Classify() with the right model. switch (type) @@ -286,7 +290,7 @@ size_t CountNodes(TreeType& tree) } // Get the number of nodes in the tree. -size_t HoeffdingTreeModel::NumNodes() const +inline size_t HoeffdingTreeModel::NumNodes() const { // Call CountNodes() with the right type of tree. switch (type) @@ -303,3 +307,8 @@ size_t HoeffdingTreeModel::NumNodes() const return 0; // This should never happen! } + +} // namespace tree +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/kde/CMakeLists.txt b/src/mlpack/methods/kde/CMakeLists.txt index e4a6f5d0c4..e438aeab88 100644 --- a/src/mlpack/methods/kde/CMakeLists.txt +++ b/src/mlpack/methods/kde/CMakeLists.txt @@ -8,7 +8,6 @@ set(SOURCES kde_stat.hpp kde_model.hpp kde_model_impl.hpp - kde_model.cpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/kde/kde_model.cpp b/src/mlpack/methods/kde/kde_model.cpp deleted file mode 100644 index 474809c822..0000000000 --- a/src/mlpack/methods/kde/kde_model.cpp +++ /dev/null @@ -1,319 +0,0 @@ -/** - * @file methods/kde/kde_model.cpp - * @author Roberto Hueso - * - * Implementation of KDE Model. - * - * 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. - */ -#include "kde_model.hpp" - -namespace mlpack { -namespace kde { - -//! Initialize the KDEModel with the given parameters. -KDEModel::KDEModel(const double bandwidth, - const double relError, - const double absError, - const KernelTypes kernelType, - const TreeTypes treeType, - const bool monteCarlo, - const double mcProb, - const size_t initialSampleSize, - const double mcEntryCoef, - const double mcBreakCoef) : - bandwidth(bandwidth), - relError(relError), - absError(absError), - kernelType(kernelType), - treeType(treeType), - monteCarlo(monteCarlo), - mcProb(mcProb), - initialSampleSize(initialSampleSize), - mcEntryCoef(mcEntryCoef), - mcBreakCoef(mcBreakCoef), - kdeModel(NULL) -{ - // Nothing to do. -} - -// Copy constructor. -KDEModel::KDEModel(const KDEModel& other) : - bandwidth(other.bandwidth), - relError(other.relError), - absError(other.absError), - kernelType(other.kernelType), - treeType(other.treeType), - monteCarlo(other.monteCarlo), - mcProb(other.mcProb), - initialSampleSize(other.initialSampleSize), - mcEntryCoef(other.mcEntryCoef), - mcBreakCoef(other.mcBreakCoef), - kdeModel(other.kdeModel->Clone()) -{ - // Nothing to do. -} - -// Move constructor. -KDEModel::KDEModel(KDEModel&& other) : - bandwidth(other.bandwidth), - relError(other.relError), - absError(other.absError), - kernelType(other.kernelType), - treeType(other.treeType), - monteCarlo(other.monteCarlo), - mcProb(other.mcProb), - initialSampleSize(other.initialSampleSize), - mcEntryCoef(other.mcEntryCoef), - mcBreakCoef(other.mcBreakCoef), - kdeModel(std::move(other.kdeModel)) -{ - // Reset other model. - other.bandwidth = 1.0; - other.relError = KDEDefaultParams::relError; - other.absError = KDEDefaultParams::absError; - other.kernelType = KernelTypes::GAUSSIAN_KERNEL; - other.treeType = TreeTypes::KD_TREE; - other.monteCarlo = KDEDefaultParams::monteCarlo; - other.mcProb = KDEDefaultParams::mcProb; - other.initialSampleSize = KDEDefaultParams::initialSampleSize; - other.mcEntryCoef = KDEDefaultParams::mcEntryCoef; - other.mcBreakCoef = KDEDefaultParams::mcBreakCoef; -} - -KDEModel& KDEModel::operator=(const KDEModel& other) -{ - if (this != &other) - { - delete kdeModel; - - bandwidth = other.bandwidth; - relError = other.relError; - absError = other.absError; - kernelType = other.kernelType; - treeType = other.treeType; - monteCarlo = other.monteCarlo; - mcProb = other.mcProb; - initialSampleSize = other.initialSampleSize; - mcEntryCoef = other.mcEntryCoef; - mcBreakCoef = other.mcBreakCoef; - kdeModel = other.kdeModel->Clone(); - } - - return *this; -} - -KDEModel& KDEModel::operator=(KDEModel&& other) -{ - if (this != &other) - { - delete kdeModel; - - bandwidth = other.bandwidth; - relError = other.relError; - absError = other.absError; - kernelType = other.kernelType; - treeType = other.treeType; - monteCarlo = other.monteCarlo; - mcProb = other.mcProb; - initialSampleSize = other.initialSampleSize; - mcEntryCoef = other.mcEntryCoef; - mcBreakCoef = other.mcBreakCoef; - kdeModel = std::move(other.kdeModel); - - // Reset other model. - other.bandwidth = 1.0; - other.relError = KDEDefaultParams::relError; - other.absError = KDEDefaultParams::absError; - other.kernelType = KernelTypes::GAUSSIAN_KERNEL; - other.treeType = TreeTypes::KD_TREE; - other.monteCarlo = KDEDefaultParams::monteCarlo; - other.mcProb = KDEDefaultParams::mcProb; - other.initialSampleSize = KDEDefaultParams::initialSampleSize; - other.mcEntryCoef = KDEDefaultParams::mcEntryCoef; - other.mcBreakCoef = KDEDefaultParams::mcBreakCoef; - } - - return *this; -} - -// Clean memory. -KDEModel::~KDEModel() -{ - delete kdeModel; -} - -template class TreeType> -KDEWrapperBase* InitializeModelHelper(const KDEModel::KernelTypes kernelType, - const double relError, - const double absError, - const double bandwidth) -{ - switch (kernelType) - { - case KDEModel::GAUSSIAN_KERNEL: - return new KDEWrapper( - relError, absError, kernel::GaussianKernel(bandwidth)); - - case KDEModel::EPANECHNIKOV_KERNEL: - return new KDEWrapper( - relError, absError, kernel::EpanechnikovKernel(bandwidth)); - - case KDEModel::LAPLACIAN_KERNEL: - return new KDEWrapper( - relError, absError, kernel::LaplacianKernel(bandwidth)); - - case KDEModel::SPHERICAL_KERNEL: - return new KDEWrapper( - relError, absError, kernel::SphericalKernel(bandwidth)); - - case KDEModel::TRIANGULAR_KERNEL: - return new KDEWrapper( - relError, absError, kernel::TriangularKernel(bandwidth)); - } - - // This should never happen. - return NULL; -} - -void KDEModel::InitializeModel() -{ - // Clean memory, if necessary. - delete kdeModel; - - // Build the actual model. - switch (treeType) - { - case KD_TREE: - kdeModel = InitializeModelHelper(kernelType, relError, - absError, bandwidth); - break; - - case BALL_TREE: - kdeModel = InitializeModelHelper(kernelType, relError, - absError, bandwidth); - break; - - case COVER_TREE: - kdeModel = InitializeModelHelper(kernelType, - relError, absError, bandwidth); - break; - - case OCTREE: - kdeModel = InitializeModelHelper(kernelType, relError, - absError, bandwidth); - break; - - case R_TREE: - kdeModel = InitializeModelHelper(kernelType, relError, - absError, bandwidth); - break; - } -} - -void KDEModel::BuildModel(util::Timers& timers, arma::mat&& referenceSet) -{ - InitializeModel(); - - // Set whether to use Monte Carlo estimations or not. - kdeModel->MonteCarlo() = monteCarlo; - - // Set Monte Carlo probability. - kdeModel->MCProb(mcProb); - - // Set Monte Carlo initial sample size. - kdeModel->MCInitialSampleSize() = initialSampleSize; - - // Set Monte Carlo entry coefficient. - kdeModel->MCEntryCoef(mcEntryCoef); - - // Set Monte Carlo break coefficient. - kdeModel->MCBreakCoef(mcBreakCoef); - - // Train the model. - kdeModel->Train(timers, std::move(referenceSet)); -} - -// Perform bichromatic evaluation. -void KDEModel::Evaluate(util::Timers& timers, - arma::mat&& querySet, - arma::vec& estimates) -{ - kdeModel->Evaluate(timers, std::move(querySet), estimates); -} - -// Perform monochromatic evaluation. -void KDEModel::Evaluate(util::Timers& timers, arma::vec& estimates) -{ - kdeModel->Evaluate(timers, estimates); -} - -// Clean memory. -void KDEModel::CleanMemory() -{ - delete kdeModel; -} - -// Modify model kernel bandwidth. -void KDEModel::Bandwidth(const double newBandwidth) -{ - bandwidth = newBandwidth; - kdeModel->Bandwidth(bandwidth); -} - -// Modify model relative error tolerance. -void KDEModel::RelativeError(const double newRelError) -{ - relError = newRelError; - kdeModel->RelativeError(relError); -} - -// Modify model absolute error tolerance. -void KDEModel::AbsoluteError(const double newAbsError) -{ - absError = newAbsError; - kdeModel->AbsoluteError(absError); -} - -// Modify whether Monte Carlo estimations will be used. -void KDEModel::MonteCarlo(const bool newMonteCarlo) -{ - monteCarlo = newMonteCarlo; - kdeModel->MonteCarlo() = monteCarlo; -} - -// Modify model Monte Carlo probability. -void KDEModel::MCProbability(const double newMCProb) -{ - mcProb = newMCProb; - kdeModel->MCProb(mcProb); -} - -// Modify model Monte Carlo initial sample size. -void KDEModel::MCInitialSampleSize(const size_t newSampleSize) -{ - initialSampleSize = newSampleSize; - kdeModel->MCInitialSampleSize() = initialSampleSize; -} - -// Modify model Monte Carlo entry coefficient. -void KDEModel::MCEntryCoefficient(const double newEntryCoef) -{ - mcEntryCoef = newEntryCoef; - kdeModel->MCEntryCoef(mcEntryCoef); -} - -// Modify model Monte Carlo break coefficient. -void KDEModel::MCBreakCoefficient(const double newBreakCoef) -{ - mcBreakCoef = newBreakCoef; - kdeModel->MCBreakCoef(mcBreakCoef); -} - -} // namespace kde -} // namespace mlpack diff --git a/src/mlpack/methods/kde/kde_model_impl.hpp b/src/mlpack/methods/kde/kde_model_impl.hpp index 8ff96ba666..4d6e075334 100644 --- a/src/mlpack/methods/kde/kde_model_impl.hpp +++ b/src/mlpack/methods/kde/kde_model_impl.hpp @@ -18,6 +18,310 @@ namespace mlpack { namespace kde { +//! Initialize the KDEModel with the given parameters. +inline KDEModel::KDEModel( + const double bandwidth, + const double relError, + const double absError, + const KernelTypes kernelType, + const TreeTypes treeType, + const bool monteCarlo, + const double mcProb, + const size_t initialSampleSize, + const double mcEntryCoef, + const double mcBreakCoef) : + bandwidth(bandwidth), + relError(relError), + absError(absError), + kernelType(kernelType), + treeType(treeType), + monteCarlo(monteCarlo), + mcProb(mcProb), + initialSampleSize(initialSampleSize), + mcEntryCoef(mcEntryCoef), + mcBreakCoef(mcBreakCoef), + kdeModel(NULL) +{ + // Nothing to do. +} + +// Copy constructor. +inline KDEModel::KDEModel(const KDEModel& other) : + bandwidth(other.bandwidth), + relError(other.relError), + absError(other.absError), + kernelType(other.kernelType), + treeType(other.treeType), + monteCarlo(other.monteCarlo), + mcProb(other.mcProb), + initialSampleSize(other.initialSampleSize), + mcEntryCoef(other.mcEntryCoef), + mcBreakCoef(other.mcBreakCoef), + kdeModel(other.kdeModel->Clone()) +{ + // Nothing to do. +} + +// Move constructor. +inline KDEModel::KDEModel(KDEModel&& other) : + bandwidth(other.bandwidth), + relError(other.relError), + absError(other.absError), + kernelType(other.kernelType), + treeType(other.treeType), + monteCarlo(other.monteCarlo), + mcProb(other.mcProb), + initialSampleSize(other.initialSampleSize), + mcEntryCoef(other.mcEntryCoef), + mcBreakCoef(other.mcBreakCoef), + kdeModel(std::move(other.kdeModel)) +{ + // Reset other model. + other.bandwidth = 1.0; + other.relError = KDEDefaultParams::relError; + other.absError = KDEDefaultParams::absError; + other.kernelType = KernelTypes::GAUSSIAN_KERNEL; + other.treeType = TreeTypes::KD_TREE; + other.monteCarlo = KDEDefaultParams::monteCarlo; + other.mcProb = KDEDefaultParams::mcProb; + other.initialSampleSize = KDEDefaultParams::initialSampleSize; + other.mcEntryCoef = KDEDefaultParams::mcEntryCoef; + other.mcBreakCoef = KDEDefaultParams::mcBreakCoef; +} + +inline KDEModel& KDEModel::operator=(const KDEModel& other) +{ + if (this != &other) + { + delete kdeModel; + + bandwidth = other.bandwidth; + relError = other.relError; + absError = other.absError; + kernelType = other.kernelType; + treeType = other.treeType; + monteCarlo = other.monteCarlo; + mcProb = other.mcProb; + initialSampleSize = other.initialSampleSize; + mcEntryCoef = other.mcEntryCoef; + mcBreakCoef = other.mcBreakCoef; + kdeModel = other.kdeModel->Clone(); + } + + return *this; +} + +inline KDEModel& KDEModel::operator=(KDEModel&& other) +{ + if (this != &other) + { + delete kdeModel; + + bandwidth = other.bandwidth; + relError = other.relError; + absError = other.absError; + kernelType = other.kernelType; + treeType = other.treeType; + monteCarlo = other.monteCarlo; + mcProb = other.mcProb; + initialSampleSize = other.initialSampleSize; + mcEntryCoef = other.mcEntryCoef; + mcBreakCoef = other.mcBreakCoef; + kdeModel = std::move(other.kdeModel); + + // Reset other model. + other.bandwidth = 1.0; + other.relError = KDEDefaultParams::relError; + other.absError = KDEDefaultParams::absError; + other.kernelType = KernelTypes::GAUSSIAN_KERNEL; + other.treeType = TreeTypes::KD_TREE; + other.monteCarlo = KDEDefaultParams::monteCarlo; + other.mcProb = KDEDefaultParams::mcProb; + other.initialSampleSize = KDEDefaultParams::initialSampleSize; + other.mcEntryCoef = KDEDefaultParams::mcEntryCoef; + other.mcBreakCoef = KDEDefaultParams::mcBreakCoef; + } + + return *this; +} + +// Clean memory. +inline KDEModel::~KDEModel() +{ + delete kdeModel; +} + +template class TreeType> +KDEWrapperBase* InitializeModelHelper(const KDEModel::KernelTypes kernelType, + const double relError, + const double absError, + const double bandwidth) +{ + switch (kernelType) + { + case KDEModel::GAUSSIAN_KERNEL: + return new KDEWrapper( + relError, absError, kernel::GaussianKernel(bandwidth)); + + case KDEModel::EPANECHNIKOV_KERNEL: + return new KDEWrapper( + relError, absError, kernel::EpanechnikovKernel(bandwidth)); + + case KDEModel::LAPLACIAN_KERNEL: + return new KDEWrapper( + relError, absError, kernel::LaplacianKernel(bandwidth)); + + case KDEModel::SPHERICAL_KERNEL: + return new KDEWrapper( + relError, absError, kernel::SphericalKernel(bandwidth)); + + case KDEModel::TRIANGULAR_KERNEL: + return new KDEWrapper( + relError, absError, kernel::TriangularKernel(bandwidth)); + } + + // This should never happen. + return NULL; +} + +inline void KDEModel::InitializeModel() +{ + // Clean memory, if necessary. + delete kdeModel; + + // Build the actual model. + switch (treeType) + { + case KD_TREE: + kdeModel = InitializeModelHelper(kernelType, relError, + absError, bandwidth); + break; + + case BALL_TREE: + kdeModel = InitializeModelHelper(kernelType, relError, + absError, bandwidth); + break; + + case COVER_TREE: + kdeModel = InitializeModelHelper(kernelType, + relError, absError, bandwidth); + break; + + case OCTREE: + kdeModel = InitializeModelHelper(kernelType, relError, + absError, bandwidth); + break; + + case R_TREE: + kdeModel = InitializeModelHelper(kernelType, relError, + absError, bandwidth); + break; + } +} + +inline void KDEModel::BuildModel(util::Timers& timers, + arma::mat&& referenceSet) +{ + InitializeModel(); + + // Set whether to use Monte Carlo estimations or not. + kdeModel->MonteCarlo() = monteCarlo; + + // Set Monte Carlo probability. + kdeModel->MCProb(mcProb); + + // Set Monte Carlo initial sample size. + kdeModel->MCInitialSampleSize() = initialSampleSize; + + // Set Monte Carlo entry coefficient. + kdeModel->MCEntryCoef(mcEntryCoef); + + // Set Monte Carlo break coefficient. + kdeModel->MCBreakCoef(mcBreakCoef); + + // Train the model. + kdeModel->Train(timers, std::move(referenceSet)); +} + +// Perform bichromatic evaluation. +inline void KDEModel::Evaluate(util::Timers& timers, + arma::mat&& querySet, + arma::vec& estimates) +{ + kdeModel->Evaluate(timers, std::move(querySet), estimates); +} + +// Perform monochromatic evaluation. +inline void KDEModel::Evaluate(util::Timers& timers, + arma::vec& estimates) +{ + kdeModel->Evaluate(timers, estimates); +} + +// Clean memory. +inline void KDEModel::CleanMemory() +{ + delete kdeModel; +} + +// Modify model kernel bandwidth. +inline void KDEModel::Bandwidth(const double newBandwidth) +{ + bandwidth = newBandwidth; + kdeModel->Bandwidth(bandwidth); +} + +// Modify model relative error tolerance. +inline void KDEModel::RelativeError(const double newRelError) +{ + relError = newRelError; + kdeModel->RelativeError(relError); +} + +// Modify model absolute error tolerance. +inline void KDEModel::AbsoluteError(const double newAbsError) +{ + absError = newAbsError; + kdeModel->AbsoluteError(absError); +} + +// Modify whether Monte Carlo estimations will be used. +inline void KDEModel::MonteCarlo(const bool newMonteCarlo) +{ + monteCarlo = newMonteCarlo; + kdeModel->MonteCarlo() = monteCarlo; +} + +// Modify model Monte Carlo probability. +inline void KDEModel::MCProbability(const double newMCProb) +{ + mcProb = newMCProb; + kdeModel->MCProb(mcProb); +} + +// Modify model Monte Carlo initial sample size. +inline void KDEModel::MCInitialSampleSize(const size_t newSampleSize) +{ + initialSampleSize = newSampleSize; + kdeModel->MCInitialSampleSize() = initialSampleSize; +} + +// Modify model Monte Carlo entry coefficient. +inline void KDEModel::MCEntryCoefficient(const double newEntryCoef) +{ + mcEntryCoef = newEntryCoef; + kdeModel->MCEntryCoef(mcEntryCoef); +} + +// Modify model Monte Carlo break coefficient. +inline void KDEModel::MCBreakCoefficient(const double newBreakCoef) +{ + mcBreakCoef = newBreakCoef; + kdeModel->MCBreakCoef(mcBreakCoef); +} + //! Train the model (build the tree). template -#include - -using namespace mlpack; -using namespace mlpack::regression; - -LARS::LARS(const bool useCholesky, - const double lambda1, - const double lambda2, - const double tolerance) : - matGram(&matGramInternal), - useCholesky(useCholesky), - lasso((lambda1 != 0)), - lambda1(lambda1), - elasticNet((lambda1 != 0) && (lambda2 != 0)), - lambda2(lambda2), - tolerance(tolerance) -{ /* Nothing left to do. */ } - -LARS::LARS(const bool useCholesky, - const arma::mat& gramMatrix, - const double lambda1, - const double lambda2, - const double tolerance) : - matGram(&gramMatrix), - useCholesky(useCholesky), - lasso((lambda1 != 0)), - lambda1(lambda1), - elasticNet((lambda1 != 0) && (lambda2 != 0)), - lambda2(lambda2), - tolerance(tolerance) -{ /* Nothing left to do */ } - -LARS::LARS(const arma::mat& data, - const arma::rowvec& responses, - const bool transposeData, - const bool useCholesky, - const double lambda1, - const double lambda2, - const double tolerance) : - LARS(useCholesky, lambda1, lambda2, tolerance) -{ - Train(data, responses, transposeData); -} - -LARS::LARS(const arma::mat& data, - const arma::rowvec& responses, - const bool transposeData, - const bool useCholesky, - const arma::mat& gramMatrix, - const double lambda1, - const double lambda2, - const double tolerance) : - LARS(useCholesky, gramMatrix, lambda1, lambda2, tolerance) -{ - Train(data, responses, transposeData); -} - -// Copy Constructor. -LARS::LARS(const LARS& other) : - matGramInternal(other.matGramInternal), - matGram(other.matGram != &other.matGramInternal ? - other.matGram : &matGramInternal), - matUtriCholFactor(other.matUtriCholFactor), - useCholesky(other.useCholesky), - lasso(other.lasso), - lambda1(other.lambda1), - elasticNet(other.elasticNet), - lambda2(other.lambda2), - tolerance(other.tolerance), - betaPath(other.betaPath), - lambdaPath(other.lambdaPath), - activeSet(other.activeSet), - isActive(other.isActive), - ignoreSet(other.ignoreSet), - isIgnored(other.isIgnored) -{ - // Nothing to do here. -} - -// Move constructor. -LARS::LARS(LARS&& other) : - matGramInternal(std::move(other.matGramInternal)), - matGram(other.matGram != &other.matGramInternal ? - other.matGram : &matGramInternal), - matUtriCholFactor(std::move(other.matUtriCholFactor)), - useCholesky(other.useCholesky), - lasso(other.lasso), - lambda1(other.lambda1), - elasticNet(other.elasticNet), - lambda2(other.lambda2), - tolerance(other.tolerance), - betaPath(std::move(other.betaPath)), - lambdaPath(std::move(other.lambdaPath)), - activeSet(std::move(other.activeSet)), - isActive(std::move(other.isActive)), - ignoreSet(std::move(other.ignoreSet)), - isIgnored(std::move(other.isIgnored)) -{ - // Nothing to do here. -} - -// Copy operator. -LARS& LARS::operator=(const LARS& other) -{ - if (&other == this) - return *this; - - matGramInternal = other.matGramInternal; - matGram = other.matGram != &other.matGramInternal ? - other.matGram : &matGramInternal; - matUtriCholFactor = other.matUtriCholFactor; - useCholesky = other.useCholesky; - lasso = other.lasso; - lambda1 = other.lambda1; - elasticNet = other.elasticNet; - lambda2 = other.lambda2; - tolerance = other.tolerance; - betaPath = other.betaPath; - lambdaPath = other.lambdaPath; - activeSet = other.activeSet; - isActive = other.isActive; - ignoreSet = other.ignoreSet; - isIgnored = other.isIgnored; - return *this; -} - -// Move Operator. -LARS& LARS::operator=(LARS&& other) -{ - if (&other == this) - return *this; - - matGramInternal = std::move(other.matGramInternal); - matGram = other.matGram != &other.matGramInternal ? - other.matGram : &matGramInternal; - matUtriCholFactor = std::move(other.matUtriCholFactor); - useCholesky = other.useCholesky; - lasso = other.lasso; - lambda1 = other.lambda1; - elasticNet = other.elasticNet; - lambda2 = other.lambda2; - tolerance = other.tolerance; - betaPath = std::move(other.betaPath); - lambdaPath = std::move(other.lambdaPath); - activeSet = std::move(other.activeSet); - isActive = std::move(other.isActive); - ignoreSet = std::move(other.ignoreSet); - isIgnored = std::move(other.isIgnored); - return *this; -} - -double LARS::Train(const arma::mat& matX, - const arma::rowvec& y, - arma::vec& beta, - const bool transposeData) -{ - // Clear any previous solution information. - betaPath.clear(); - lambdaPath.clear(); - activeSet.clear(); - isActive.clear(); - ignoreSet.clear(); - isIgnored.clear(); - matUtriCholFactor.reset(); - - // Update values in case lambda1 or lambda2 changed. - lasso = (lambda1 != 0); - elasticNet = (lambda1 != 0 && lambda2 != 0); - - // This matrix may end up holding the transpose -- if necessary. - arma::mat dataTrans; - // dataRef is row-major. - const arma::mat& dataRef = (transposeData ? dataTrans : matX); - if (transposeData) - dataTrans = trans(matX); - - // Compute X' * y. - arma::vec vecXTy = trans(y * dataRef); - - // Set up active set variables. In the beginning, the active set has size 0 - // (all dimensions are inactive). - isActive.resize(dataRef.n_cols, false); - - // Set up ignores set variables. Initialized empty. - isIgnored.resize(dataRef.n_cols, false); - - // Initialize yHat and beta. - beta = arma::zeros(dataRef.n_cols); - arma::vec yHat = arma::zeros(dataRef.n_rows); - arma::vec yHatDirection(dataRef.n_rows); - - bool lassocond = false; - - // Compute the initial maximum correlation among all dimensions. - arma::vec corr = vecXTy; - double maxCorr = 0; - size_t changeInd = 0; - for (size_t i = 0; i < vecXTy.n_elem; ++i) - { - if (fabs(corr(i)) > maxCorr) - { - maxCorr = fabs(corr(i)); - changeInd = i; - } - } - - betaPath.push_back(beta); - lambdaPath.push_back(maxCorr); - - // If the maximum correlation is too small, there is no reason to continue. - if (maxCorr < lambda1) - { - lambdaPath[0] = lambda1; - return maxCorr; - } - - // Compute the Gram matrix. If this is the elastic net problem, we will add - // lambda2 * I_n to the matrix. - if (matGram->n_elem != dataRef.n_cols * dataRef.n_cols) - { - // In this case, matGram should reference matGramInternal. - matGramInternal = trans(dataRef) * dataRef; - - if (elasticNet && !useCholesky) - matGramInternal += lambda2 * arma::eye(dataRef.n_cols, dataRef.n_cols); - } - - // Main loop. - while (((activeSet.size() + ignoreSet.size()) < dataRef.n_cols) && - (maxCorr > tolerance)) - { - // Compute the maximum correlation among inactive dimensions. - maxCorr = 0; - for (size_t i = 0; i < dataRef.n_cols; ++i) - { - if ((!isActive[i]) && (!isIgnored[i]) && (fabs(corr(i)) > maxCorr)) - { - maxCorr = fabs(corr(i)); - changeInd = i; - } - } - - if (!lassocond) - { - if (useCholesky) - { - // vec newGramCol = vec(activeSet.size()); - // for (size_t i = 0; i < activeSet.size(); ++i) - // { - // newGramCol[i] = dot(matX.col(activeSet[i]), matX.col(changeInd)); - // } - // This is equivalent to the above 5 lines. - arma::vec newGramCol = matGram->elem(changeInd * dataRef.n_cols + - arma::conv_to::from(activeSet)); - - CholeskyInsert((*matGram)(changeInd, changeInd), newGramCol); - } - - // Add variable to active set. - Activate(changeInd); - } - - // Compute signs of correlations. - arma::vec s = arma::vec(activeSet.size()); - for (size_t i = 0; i < activeSet.size(); ++i) - s(i) = corr(activeSet[i]) / fabs(corr(activeSet[i])); - - // Compute the "equiangular" direction in parameter space (betaDirection). - // We use quotes because in the case of non-unit norm variables, this need - // not be equiangular. - arma::vec unnormalizedBetaDirection; - double normalization; - arma::vec betaDirection; - if (useCholesky) - { - // Check for singularity. - const double lastUtriElement = matUtriCholFactor( - matUtriCholFactor.n_cols - 1, matUtriCholFactor.n_rows - 1); - if (std::abs(lastUtriElement) > tolerance) - { - // Ok, no singularity. - /** - * Note that: - * R^T R % S^T % S = (R % S)^T (R % S) - * Now, for 1 the ones vector: - * inv( (R % S)^T (R % S) ) 1 - * = inv(R % S) inv((R % S)^T) 1 - * = inv(R % S) Solve((R % S)^T, 1) - * = inv(R % S) Solve(R^T, s) - * = Solve(R % S, Solve(R^T, s) - * = s % Solve(R, Solve(R^T, s)) - */ - unnormalizedBetaDirection = solve(trimatu(matUtriCholFactor), - solve(trimatl(trans(matUtriCholFactor)), s)); - - normalization = 1.0 / sqrt(dot(s, unnormalizedBetaDirection)); - betaDirection = normalization * unnormalizedBetaDirection; - } - else - { - // Singularity, so remove variable from active set, add to ignores set, - // and look for new variable to add. - Log::Warn << "Encountered singularity when adding variable " - << changeInd << " to active set; permanently removing." - << std::endl; - Deactivate(activeSet.size() - 1); - Ignore(changeInd); - CholeskyDelete(matUtriCholFactor.n_rows - 1); - continue; - } - } - else - { - arma::mat matGramActive = arma::mat(activeSet.size(), activeSet.size()); - for (size_t i = 0; i < activeSet.size(); ++i) - for (size_t j = 0; j < activeSet.size(); ++j) - matGramActive(i, j) = (*matGram)(activeSet[i], activeSet[j]); - - // Check for singularity. - arma::mat matS = s * arma::ones(1, activeSet.size()); - const bool solvedOk = solve(unnormalizedBetaDirection, - matGramActive % trans(matS) % matS, - arma::ones(activeSet.size(), 1)); - if (solvedOk) - { - // Ok, no singularity. - normalization = 1.0 / sqrt(sum(unnormalizedBetaDirection)); - betaDirection = normalization * unnormalizedBetaDirection % s; - } - else - { - // Singularity, so remove variable from active set, add to ignores set, - // and look for new variable to add. - Deactivate(activeSet.size() - 1); - Ignore(changeInd); - Log::Warn << "Encountered singularity when adding variable " - << changeInd << " to active set; permanently removing." - << std::endl; - continue; - } - } - - // compute "equiangular" direction in output space - ComputeYHatDirection(dataRef, betaDirection, yHatDirection); - - double gamma = maxCorr / normalization; - - // If not all variables are active. - if ((activeSet.size() + ignoreSet.size()) < dataRef.n_cols) - { - // Compute correlations with direction. - for (size_t ind = 0; ind < dataRef.n_cols; ind++) - { - if (isActive[ind] || isIgnored[ind]) - continue; - - const double dirCorr = dot(dataRef.col(ind), yHatDirection); - const double val1 = (maxCorr - corr(ind)) / (normalization - dirCorr); - const double val2 = (maxCorr + corr(ind)) / (normalization + dirCorr); - if ((val1 > 0.0) && (val1 < gamma)) - gamma = val1; - if ((val2 > 0.0) && (val2 < gamma)) - gamma = val2; - // Handle edge case where the largest actually is equal to 0. - if (std::max(val1, val2) == 0.0) - gamma = 0.0; - } - } - - // Bound gamma according to LASSO. - if (lasso) - { - lassocond = false; - double lassoboundOnGamma = DBL_MAX; - size_t activeIndToKickOut = -1; - - for (size_t i = 0; i < activeSet.size(); ++i) - { - double val = -beta(activeSet[i]) / betaDirection(i); - if ((val > 0) && (val < lassoboundOnGamma)) - { - lassoboundOnGamma = val; - activeIndToKickOut = i; - } - } - - if (lassoboundOnGamma < gamma) - { - gamma = lassoboundOnGamma; - lassocond = true; - changeInd = activeIndToKickOut; - } - } - - // Update the prediction. - yHat += gamma * yHatDirection; - - // Update the estimator. - for (size_t i = 0; i < activeSet.size(); ++i) - { - beta(activeSet[i]) += gamma * betaDirection(i); - } - - // Sanity check to make sure the kicked out dimension is actually zero. - if (lassocond) - { - if (beta(activeSet[changeInd]) != 0) - beta(activeSet[changeInd]) = 0; - } - - betaPath.push_back(beta); - - if (lassocond) - { - // Index is in position changeInd in activeSet. - if (useCholesky) - CholeskyDelete(changeInd); - - Deactivate(changeInd); - } - - corr = vecXTy - trans(dataRef) * yHat; - if (elasticNet) - corr -= lambda2 * beta; - - double curLambda = 0; - for (size_t i = 0; i < activeSet.size(); ++i) - curLambda += fabs(corr(activeSet[i])); - - curLambda /= ((double) activeSet.size()); - - lambdaPath.push_back(curLambda); - - // Time to stop for LASSO? - if (lasso) - { - if (curLambda <= lambda1) - { - InterpolateBeta(); - break; - } - } - } - - // Unfortunate copy... - beta = betaPath.back(); - - return ComputeError(matX, y, !transposeData); -} - -double LARS::Train(const arma::mat& data, - const arma::rowvec& responses, - const bool transposeData) -{ - arma::vec beta; - return Train(data, responses, beta, transposeData); -} - -void LARS::Predict(const arma::mat& points, - arma::rowvec& predictions, - const bool rowMajor) const -{ - // We really only need to store beta internally... - if (rowMajor) - predictions = trans(points * betaPath.back()); - else - predictions = betaPath.back().t() * points; -} - -// Private functions. -void LARS::Deactivate(const size_t activeVarInd) -{ - isActive[activeSet[activeVarInd]] = false; - activeSet.erase(activeSet.begin() + activeVarInd); -} - -void LARS::Activate(const size_t varInd) -{ - isActive[varInd] = true; - activeSet.push_back(varInd); -} - -void LARS::Ignore(const size_t varInd) -{ - isIgnored[varInd] = true; - ignoreSet.push_back(varInd); -} - -void LARS::ComputeYHatDirection(const arma::mat& matX, - const arma::vec& betaDirection, - arma::vec& yHatDirection) -{ - yHatDirection.fill(0); - for (size_t i = 0; i < activeSet.size(); ++i) - yHatDirection += betaDirection(i) * matX.col(activeSet[i]); -} - -void LARS::InterpolateBeta() -{ - int pathLength = betaPath.size(); - - // interpolate beta and stop - double ultimateLambda = lambdaPath[pathLength - 1]; - double penultimateLambda = lambdaPath[pathLength - 2]; - double interp = (penultimateLambda - lambda1) - / (penultimateLambda - ultimateLambda); - - betaPath[pathLength - 1] = (1 - interp) * (betaPath[pathLength - 2]) - + interp * betaPath[pathLength - 1]; - - lambdaPath[pathLength - 1] = lambda1; -} - -void LARS::CholeskyInsert(const arma::vec& newX, const arma::mat& X) -{ - if (matUtriCholFactor.n_rows == 0) - { - matUtriCholFactor = arma::mat(1, 1); - - if (elasticNet) - matUtriCholFactor(0, 0) = sqrt(dot(newX, newX) + lambda2); - else - matUtriCholFactor(0, 0) = norm(newX, 2); - } - else - { - arma::vec newGramCol = trans(X) * newX; - CholeskyInsert(dot(newX, newX), newGramCol); - } -} - -void LARS::CholeskyInsert(double sqNormNewX, const arma::vec& newGramCol) -{ - int n = matUtriCholFactor.n_rows; - - if (n == 0) - { - matUtriCholFactor = arma::mat(1, 1); - - if (elasticNet) - matUtriCholFactor(0, 0) = sqrt(sqNormNewX + lambda2); - else - matUtriCholFactor(0, 0) = sqrt(sqNormNewX); - } - else - { - arma::mat matNewR = arma::mat(n + 1, n + 1); - - if (elasticNet) - sqNormNewX += lambda2; - - arma::vec matUtriCholFactork = solve(trimatl(trans(matUtriCholFactor)), - newGramCol); - - matNewR(arma::span(0, n - 1), arma::span(0, n - 1)) = matUtriCholFactor; - matNewR(arma::span(0, n - 1), n) = matUtriCholFactork; - matNewR(n, arma::span(0, n - 1)).fill(0.0); - matNewR(n, n) = sqrt(sqNormNewX - dot(matUtriCholFactork, - matUtriCholFactork)); - - matUtriCholFactor = matNewR; - } -} - -void LARS::GivensRotate(const arma::vec::fixed<2>& x, - arma::vec::fixed<2>& rotatedX, - arma::mat& matG) -{ - if (x(1) == 0) - { - matG = arma::eye(2, 2); - rotatedX = x; - } - else - { - double r = norm(x, 2); - matG = arma::mat(2, 2); - - double scaledX1 = x(0) / r; - double scaledX2 = x(1) / r; - - matG(0, 0) = scaledX1; - matG(1, 0) = -scaledX2; - matG(0, 1) = scaledX2; - matG(1, 1) = scaledX1; - - rotatedX = arma::vec(2); - rotatedX(0) = r; - rotatedX(1) = 0; - } -} - -void LARS::CholeskyDelete(const size_t colToKill) -{ - size_t n = matUtriCholFactor.n_rows; - - if (colToKill == (n - 1)) - { - matUtriCholFactor = matUtriCholFactor(arma::span(0, n - 2), - arma::span(0, n - 2)); - } - else - { - matUtriCholFactor.shed_col(colToKill); // remove column colToKill - n--; - - for (size_t k = colToKill; k < n; ++k) - { - arma::mat matG; - arma::vec::fixed<2> rotatedVec; - GivensRotate(matUtriCholFactor(arma::span(k, k + 1), k), rotatedVec, - matG); - matUtriCholFactor(arma::span(k, k + 1), k) = rotatedVec; - if (k < n - 1) - { - matUtriCholFactor(arma::span(k, k + 1), arma::span(k + 1, n - 1)) = - matG * matUtriCholFactor(arma::span(k, k + 1), - arma::span(k + 1, n - 1)); - } - } - - matUtriCholFactor.shed_row(n); - } -} - -double LARS::ComputeError(const arma::mat& matX, - const arma::rowvec& y, - const bool rowMajor) -{ - if (rowMajor) - { - return arma::accu(arma::pow(y - trans(matX * betaPath.back()), 2.0)); - } - - else - { - return arma::accu(arma::pow(y - betaPath.back().t() * matX, 2.0)); - } -} diff --git a/src/mlpack/methods/lars/lars.hpp b/src/mlpack/methods/lars/lars.hpp index d019fec900..37dd212507 100644 --- a/src/mlpack/methods/lars/lars.hpp +++ b/src/mlpack/methods/lars/lars.hpp @@ -25,6 +25,8 @@ #define MLPACK_METHODS_LARS_LARS_HPP #include +#include +#include namespace mlpack { namespace regression { diff --git a/src/mlpack/methods/lars/lars_impl.hpp b/src/mlpack/methods/lars/lars_impl.hpp index 2b3edaea15..a3ed10a63c 100644 --- a/src/mlpack/methods/lars/lars_impl.hpp +++ b/src/mlpack/methods/lars/lars_impl.hpp @@ -18,6 +18,647 @@ namespace mlpack { namespace regression { +inline LARS::LARS( + const bool useCholesky, + const double lambda1, + const double lambda2, + const double tolerance) : + matGram(&matGramInternal), + useCholesky(useCholesky), + lasso((lambda1 != 0)), + lambda1(lambda1), + elasticNet((lambda1 != 0) && (lambda2 != 0)), + lambda2(lambda2), + tolerance(tolerance) +{ /* Nothing left to do. */ } + +inline LARS::LARS( + const bool useCholesky, + const arma::mat& gramMatrix, + const double lambda1, + const double lambda2, + const double tolerance) : + matGram(&gramMatrix), + useCholesky(useCholesky), + lasso((lambda1 != 0)), + lambda1(lambda1), + elasticNet((lambda1 != 0) && (lambda2 != 0)), + lambda2(lambda2), + tolerance(tolerance) +{ /* Nothing left to do */ } + +inline LARS::LARS( + const arma::mat& data, + const arma::rowvec& responses, + const bool transposeData, + const bool useCholesky, + const double lambda1, + const double lambda2, + const double tolerance) : + LARS(useCholesky, lambda1, lambda2, tolerance) +{ + Train(data, responses, transposeData); +} + +inline LARS::LARS( + const arma::mat& data, + const arma::rowvec& responses, + const bool transposeData, + const bool useCholesky, + const arma::mat& gramMatrix, + const double lambda1, + const double lambda2, + const double tolerance) : + LARS(useCholesky, gramMatrix, lambda1, lambda2, tolerance) +{ + Train(data, responses, transposeData); +} + +// Copy Constructor. +inline LARS::LARS(const LARS& other) : + matGramInternal(other.matGramInternal), + matGram(other.matGram != &other.matGramInternal ? + other.matGram : &matGramInternal), + matUtriCholFactor(other.matUtriCholFactor), + useCholesky(other.useCholesky), + lasso(other.lasso), + lambda1(other.lambda1), + elasticNet(other.elasticNet), + lambda2(other.lambda2), + tolerance(other.tolerance), + betaPath(other.betaPath), + lambdaPath(other.lambdaPath), + activeSet(other.activeSet), + isActive(other.isActive), + ignoreSet(other.ignoreSet), + isIgnored(other.isIgnored) +{ + // Nothing to do here. +} + +// Move constructor. +inline LARS::LARS(LARS&& other) : + matGramInternal(std::move(other.matGramInternal)), + matGram(other.matGram != &other.matGramInternal ? + other.matGram : &matGramInternal), + matUtriCholFactor(std::move(other.matUtriCholFactor)), + useCholesky(other.useCholesky), + lasso(other.lasso), + lambda1(other.lambda1), + elasticNet(other.elasticNet), + lambda2(other.lambda2), + tolerance(other.tolerance), + betaPath(std::move(other.betaPath)), + lambdaPath(std::move(other.lambdaPath)), + activeSet(std::move(other.activeSet)), + isActive(std::move(other.isActive)), + ignoreSet(std::move(other.ignoreSet)), + isIgnored(std::move(other.isIgnored)) +{ + // Nothing to do here. +} + +// Copy operator. +inline LARS& LARS::operator=(const LARS& other) +{ + if (&other == this) + return *this; + + matGramInternal = other.matGramInternal; + matGram = other.matGram != &other.matGramInternal ? + other.matGram : &matGramInternal; + matUtriCholFactor = other.matUtriCholFactor; + useCholesky = other.useCholesky; + lasso = other.lasso; + lambda1 = other.lambda1; + elasticNet = other.elasticNet; + lambda2 = other.lambda2; + tolerance = other.tolerance; + betaPath = other.betaPath; + lambdaPath = other.lambdaPath; + activeSet = other.activeSet; + isActive = other.isActive; + ignoreSet = other.ignoreSet; + isIgnored = other.isIgnored; + return *this; +} + +// Move Operator. +inline LARS& LARS::operator=(LARS&& other) +{ + if (&other == this) + return *this; + + matGramInternal = std::move(other.matGramInternal); + matGram = other.matGram != &other.matGramInternal ? + other.matGram : &matGramInternal; + matUtriCholFactor = std::move(other.matUtriCholFactor); + useCholesky = other.useCholesky; + lasso = other.lasso; + lambda1 = other.lambda1; + elasticNet = other.elasticNet; + lambda2 = other.lambda2; + tolerance = other.tolerance; + betaPath = std::move(other.betaPath); + lambdaPath = std::move(other.lambdaPath); + activeSet = std::move(other.activeSet); + isActive = std::move(other.isActive); + ignoreSet = std::move(other.ignoreSet); + isIgnored = std::move(other.isIgnored); + return *this; +} + +inline double LARS::Train(const arma::mat& matX, + const arma::rowvec& y, + arma::vec& beta, + const bool transposeData) +{ + // Clear any previous solution information. + betaPath.clear(); + lambdaPath.clear(); + activeSet.clear(); + isActive.clear(); + ignoreSet.clear(); + isIgnored.clear(); + matUtriCholFactor.reset(); + + // Update values in case lambda1 or lambda2 changed. + lasso = (lambda1 != 0); + elasticNet = (lambda1 != 0 && lambda2 != 0); + + // This matrix may end up holding the transpose -- if necessary. + arma::mat dataTrans; + // dataRef is row-major. + const arma::mat& dataRef = (transposeData ? dataTrans : matX); + if (transposeData) + dataTrans = trans(matX); + + // Compute X' * y. + arma::vec vecXTy = trans(y * dataRef); + + // Set up active set variables. In the beginning, the active set has size 0 + // (all dimensions are inactive). + isActive.resize(dataRef.n_cols, false); + + // Set up ignores set variables. Initialized empty. + isIgnored.resize(dataRef.n_cols, false); + + // Initialize yHat and beta. + beta = arma::zeros(dataRef.n_cols); + arma::vec yHat = arma::zeros(dataRef.n_rows); + arma::vec yHatDirection(dataRef.n_rows); + + bool lassocond = false; + + // Compute the initial maximum correlation among all dimensions. + arma::vec corr = vecXTy; + double maxCorr = 0; + size_t changeInd = 0; + for (size_t i = 0; i < vecXTy.n_elem; ++i) + { + if (fabs(corr(i)) > maxCorr) + { + maxCorr = fabs(corr(i)); + changeInd = i; + } + } + + betaPath.push_back(beta); + lambdaPath.push_back(maxCorr); + + // If the maximum correlation is too small, there is no reason to continue. + if (maxCorr < lambda1) + { + lambdaPath[0] = lambda1; + return maxCorr; + } + + // Compute the Gram matrix. If this is the elastic net problem, we will add + // lambda2 * I_n to the matrix. + if (matGram->n_elem != dataRef.n_cols * dataRef.n_cols) + { + // In this case, matGram should reference matGramInternal. + matGramInternal = trans(dataRef) * dataRef; + + if (elasticNet && !useCholesky) + matGramInternal += lambda2 * arma::eye(dataRef.n_cols, dataRef.n_cols); + } + + // Main loop. + while (((activeSet.size() + ignoreSet.size()) < dataRef.n_cols) && + (maxCorr > tolerance)) + { + // Compute the maximum correlation among inactive dimensions. + maxCorr = 0; + for (size_t i = 0; i < dataRef.n_cols; ++i) + { + if ((!isActive[i]) && (!isIgnored[i]) && (fabs(corr(i)) > maxCorr)) + { + maxCorr = fabs(corr(i)); + changeInd = i; + } + } + + if (!lassocond) + { + if (useCholesky) + { + // vec newGramCol = vec(activeSet.size()); + // for (size_t i = 0; i < activeSet.size(); ++i) + // { + // newGramCol[i] = dot(matX.col(activeSet[i]), matX.col(changeInd)); + // } + // This is equivalent to the above 5 lines. + arma::vec newGramCol = matGram->elem(changeInd * dataRef.n_cols + + arma::conv_to::from(activeSet)); + + CholeskyInsert((*matGram)(changeInd, changeInd), newGramCol); + } + + // Add variable to active set. + Activate(changeInd); + } + + // Compute signs of correlations. + arma::vec s = arma::vec(activeSet.size()); + for (size_t i = 0; i < activeSet.size(); ++i) + s(i) = corr(activeSet[i]) / fabs(corr(activeSet[i])); + + // Compute the "equiangular" direction in parameter space (betaDirection). + // We use quotes because in the case of non-unit norm variables, this need + // not be equiangular. + arma::vec unnormalizedBetaDirection; + double normalization; + arma::vec betaDirection; + if (useCholesky) + { + // Check for singularity. + const double lastUtriElement = matUtriCholFactor( + matUtriCholFactor.n_cols - 1, matUtriCholFactor.n_rows - 1); + if (std::abs(lastUtriElement) > tolerance) + { + // Ok, no singularity. + /** + * Note that: + * R^T R % S^T % S = (R % S)^T (R % S) + * Now, for 1 the ones vector: + * inv( (R % S)^T (R % S) ) 1 + * = inv(R % S) inv((R % S)^T) 1 + * = inv(R % S) Solve((R % S)^T, 1) + * = inv(R % S) Solve(R^T, s) + * = Solve(R % S, Solve(R^T, s) + * = s % Solve(R, Solve(R^T, s)) + */ + unnormalizedBetaDirection = solve(trimatu(matUtriCholFactor), + solve(trimatl(trans(matUtriCholFactor)), s)); + + normalization = 1.0 / sqrt(dot(s, unnormalizedBetaDirection)); + betaDirection = normalization * unnormalizedBetaDirection; + } + else + { + // Singularity, so remove variable from active set, add to ignores set, + // and look for new variable to add. + Log::Warn << "Encountered singularity when adding variable " + << changeInd << " to active set; permanently removing." + << std::endl; + Deactivate(activeSet.size() - 1); + Ignore(changeInd); + CholeskyDelete(matUtriCholFactor.n_rows - 1); + continue; + } + } + else + { + arma::mat matGramActive = arma::mat(activeSet.size(), activeSet.size()); + for (size_t i = 0; i < activeSet.size(); ++i) + for (size_t j = 0; j < activeSet.size(); ++j) + matGramActive(i, j) = (*matGram)(activeSet[i], activeSet[j]); + + // Check for singularity. + arma::mat matS = s * arma::ones(1, activeSet.size()); + const bool solvedOk = solve(unnormalizedBetaDirection, + matGramActive % trans(matS) % matS, + arma::ones(activeSet.size(), 1)); + if (solvedOk) + { + // Ok, no singularity. + normalization = 1.0 / sqrt(sum(unnormalizedBetaDirection)); + betaDirection = normalization * unnormalizedBetaDirection % s; + } + else + { + // Singularity, so remove variable from active set, add to ignores set, + // and look for new variable to add. + Deactivate(activeSet.size() - 1); + Ignore(changeInd); + Log::Warn << "Encountered singularity when adding variable " + << changeInd << " to active set; permanently removing." + << std::endl; + continue; + } + } + + // compute "equiangular" direction in output space + ComputeYHatDirection(dataRef, betaDirection, yHatDirection); + + double gamma = maxCorr / normalization; + + // If not all variables are active. + if ((activeSet.size() + ignoreSet.size()) < dataRef.n_cols) + { + // Compute correlations with direction. + for (size_t ind = 0; ind < dataRef.n_cols; ind++) + { + if (isActive[ind] || isIgnored[ind]) + continue; + + const double dirCorr = dot(dataRef.col(ind), yHatDirection); + const double val1 = (maxCorr - corr(ind)) / (normalization - dirCorr); + const double val2 = (maxCorr + corr(ind)) / (normalization + dirCorr); + if ((val1 > 0.0) && (val1 < gamma)) + gamma = val1; + if ((val2 > 0.0) && (val2 < gamma)) + gamma = val2; + // Handle edge case where the largest actually is equal to 0. + if (std::max(val1, val2) == 0.0) + gamma = 0.0; + } + } + + // Bound gamma according to LASSO. + if (lasso) + { + lassocond = false; + double lassoboundOnGamma = DBL_MAX; + size_t activeIndToKickOut = -1; + + for (size_t i = 0; i < activeSet.size(); ++i) + { + double val = -beta(activeSet[i]) / betaDirection(i); + if ((val > 0) && (val < lassoboundOnGamma)) + { + lassoboundOnGamma = val; + activeIndToKickOut = i; + } + } + + if (lassoboundOnGamma < gamma) + { + gamma = lassoboundOnGamma; + lassocond = true; + changeInd = activeIndToKickOut; + } + } + + // Update the prediction. + yHat += gamma * yHatDirection; + + // Update the estimator. + for (size_t i = 0; i < activeSet.size(); ++i) + { + beta(activeSet[i]) += gamma * betaDirection(i); + } + + // Sanity check to make sure the kicked out dimension is actually zero. + if (lassocond) + { + if (beta(activeSet[changeInd]) != 0) + beta(activeSet[changeInd]) = 0; + } + + betaPath.push_back(beta); + + if (lassocond) + { + // Index is in position changeInd in activeSet. + if (useCholesky) + CholeskyDelete(changeInd); + + Deactivate(changeInd); + } + + corr = vecXTy - trans(dataRef) * yHat; + if (elasticNet) + corr -= lambda2 * beta; + + double curLambda = 0; + for (size_t i = 0; i < activeSet.size(); ++i) + curLambda += fabs(corr(activeSet[i])); + + curLambda /= ((double) activeSet.size()); + + lambdaPath.push_back(curLambda); + + // Time to stop for LASSO? + if (lasso) + { + if (curLambda <= lambda1) + { + InterpolateBeta(); + break; + } + } + } + + // Unfortunate copy... + beta = betaPath.back(); + + return ComputeError(matX, y, !transposeData); +} + +inline double LARS::Train(const arma::mat& data, + const arma::rowvec& responses, + const bool transposeData) +{ + arma::vec beta; + return Train(data, responses, beta, transposeData); +} + +inline void LARS::Predict(const arma::mat& points, + arma::rowvec& predictions, + const bool rowMajor) const +{ + // We really only need to store beta internally... + if (rowMajor) + predictions = trans(points * betaPath.back()); + else + predictions = betaPath.back().t() * points; +} + +// Private functions. +inline void LARS::Deactivate(const size_t activeVarInd) +{ + isActive[activeSet[activeVarInd]] = false; + activeSet.erase(activeSet.begin() + activeVarInd); +} + +inline void LARS::Activate(const size_t varInd) +{ + isActive[varInd] = true; + activeSet.push_back(varInd); +} + +inline void LARS::Ignore(const size_t varInd) +{ + isIgnored[varInd] = true; + ignoreSet.push_back(varInd); +} + +inline void LARS::ComputeYHatDirection(const arma::mat& matX, + const arma::vec& betaDirection, + arma::vec& yHatDirection) +{ + yHatDirection.fill(0); + for (size_t i = 0; i < activeSet.size(); ++i) + yHatDirection += betaDirection(i) * matX.col(activeSet[i]); +} + +inline void LARS::InterpolateBeta() +{ + int pathLength = betaPath.size(); + + // interpolate beta and stop + double ultimateLambda = lambdaPath[pathLength - 1]; + double penultimateLambda = lambdaPath[pathLength - 2]; + double interp = (penultimateLambda - lambda1) + / (penultimateLambda - ultimateLambda); + + betaPath[pathLength - 1] = (1 - interp) * (betaPath[pathLength - 2]) + + interp * betaPath[pathLength - 1]; + + lambdaPath[pathLength - 1] = lambda1; +} + +inline void LARS::CholeskyInsert(const arma::vec& newX, + const arma::mat& X) +{ + if (matUtriCholFactor.n_rows == 0) + { + matUtriCholFactor = arma::mat(1, 1); + + if (elasticNet) + matUtriCholFactor(0, 0) = sqrt(dot(newX, newX) + lambda2); + else + matUtriCholFactor(0, 0) = norm(newX, 2); + } + else + { + arma::vec newGramCol = trans(X) * newX; + CholeskyInsert(dot(newX, newX), newGramCol); + } +} + +inline void LARS::CholeskyInsert(double sqNormNewX, + const arma::vec& newGramCol) +{ + int n = matUtriCholFactor.n_rows; + + if (n == 0) + { + matUtriCholFactor = arma::mat(1, 1); + + if (elasticNet) + matUtriCholFactor(0, 0) = sqrt(sqNormNewX + lambda2); + else + matUtriCholFactor(0, 0) = sqrt(sqNormNewX); + } + else + { + arma::mat matNewR = arma::mat(n + 1, n + 1); + + if (elasticNet) + sqNormNewX += lambda2; + + arma::vec matUtriCholFactork = solve(trimatl(trans(matUtriCholFactor)), + newGramCol); + + matNewR(arma::span(0, n - 1), arma::span(0, n - 1)) = matUtriCholFactor; + matNewR(arma::span(0, n - 1), n) = matUtriCholFactork; + matNewR(n, arma::span(0, n - 1)).fill(0.0); + matNewR(n, n) = sqrt(sqNormNewX - dot(matUtriCholFactork, + matUtriCholFactork)); + + matUtriCholFactor = matNewR; + } +} + +inline void LARS::GivensRotate(const arma::vec::fixed<2>& x, + arma::vec::fixed<2>& rotatedX, + arma::mat& matG) +{ + if (x(1) == 0) + { + matG = arma::eye(2, 2); + rotatedX = x; + } + else + { + double r = norm(x, 2); + matG = arma::mat(2, 2); + + double scaledX1 = x(0) / r; + double scaledX2 = x(1) / r; + + matG(0, 0) = scaledX1; + matG(1, 0) = -scaledX2; + matG(0, 1) = scaledX2; + matG(1, 1) = scaledX1; + + rotatedX = arma::vec(2); + rotatedX(0) = r; + rotatedX(1) = 0; + } +} + +inline void LARS::CholeskyDelete(const size_t colToKill) +{ + size_t n = matUtriCholFactor.n_rows; + + if (colToKill == (n - 1)) + { + matUtriCholFactor = matUtriCholFactor(arma::span(0, n - 2), + arma::span(0, n - 2)); + } + else + { + matUtriCholFactor.shed_col(colToKill); // remove column colToKill + n--; + + for (size_t k = colToKill; k < n; ++k) + { + arma::mat matG; + arma::vec::fixed<2> rotatedVec; + GivensRotate(matUtriCholFactor(arma::span(k, k + 1), k), rotatedVec, + matG); + matUtriCholFactor(arma::span(k, k + 1), k) = rotatedVec; + if (k < n - 1) + { + matUtriCholFactor(arma::span(k, k + 1), arma::span(k + 1, n - 1)) = + matG * matUtriCholFactor(arma::span(k, k + 1), + arma::span(k + 1, n - 1)); + } + } + + matUtriCholFactor.shed_row(n); + } +} + +inline double LARS::ComputeError(const arma::mat& matX, + const arma::rowvec& y, + const bool rowMajor) +{ + if (rowMajor) + { + return arma::accu(arma::pow(y - trans(matX * betaPath.back()), 2.0)); + } + + else + { + return arma::accu(arma::pow(y - betaPath.back().t() * matX, 2.0)); + } +} + /** * Serialize the LARS model. */ diff --git a/src/mlpack/methods/linear_regression/CMakeLists.txt b/src/mlpack/methods/linear_regression/CMakeLists.txt index 4e39c5e42b..da87ce69a7 100644 --- a/src/mlpack/methods/linear_regression/CMakeLists.txt +++ b/src/mlpack/methods/linear_regression/CMakeLists.txt @@ -3,7 +3,7 @@ # Do not include test programs here set(SOURCES linear_regression.hpp - linear_regression.cpp + linear_regression_impl.hpp ) # add directory name to sources diff --git a/src/mlpack/methods/linear_regression/linear_regression.hpp b/src/mlpack/methods/linear_regression/linear_regression.hpp index 3ce6f78d4c..e9baaa8966 100644 --- a/src/mlpack/methods/linear_regression/linear_regression.hpp +++ b/src/mlpack/methods/linear_regression/linear_regression.hpp @@ -14,6 +14,8 @@ #define MLPACK_METHODS_LINEAR_REGRESSION_LINEAR_REGRESSION_HPP #include +#include +#include namespace mlpack { namespace regression /** Regression methods. */ { @@ -167,4 +169,7 @@ class LinearRegression } // namespace regression } // namespace mlpack +// Include implementation. +#include "linear_regression_impl.hpp" + #endif // MLPACK_METHODS_LINEAR_REGRESSION_HPP diff --git a/src/mlpack/methods/linear_regression/linear_regression.cpp b/src/mlpack/methods/linear_regression/linear_regression_impl.hpp similarity index 76% rename from src/mlpack/methods/linear_regression/linear_regression.cpp rename to src/mlpack/methods/linear_regression/linear_regression_impl.hpp index 774425ebc7..e6d7428318 100644 --- a/src/mlpack/methods/linear_regression/linear_regression.cpp +++ b/src/mlpack/methods/linear_regression/linear_regression_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/linear_regression/linear_regression.cpp + * @file methods/linear_regression/linear_regression_impl.hpp * @author James Cline * @author Michael Fox * @@ -10,42 +10,45 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_METHODS_LINEAR_REGRESSION_LINEAR_REGRESSION_IMPL_HPP +#define MLPACK_METHODS_LINEAR_REGRESSION_LINEAR_REGRESSION_IMPL_HPP + #include "linear_regression.hpp" -#include -#include -using namespace mlpack; -using namespace mlpack::regression; +namespace mlpack { +namespace regression { -LinearRegression::LinearRegression(const arma::mat& predictors, - const arma::rowvec& responses, - const double lambda, - const bool intercept) : +inline LinearRegression::LinearRegression( + const arma::mat& predictors, + const arma::rowvec& responses, + const double lambda, + const bool intercept) : LinearRegression(predictors, responses, arma::rowvec(), lambda, intercept) -{} +{ /* Nothing to do. */ } -LinearRegression::LinearRegression(const arma::mat& predictors, - const arma::rowvec& responses, - const arma::rowvec& weights, - const double lambda, - const bool intercept) : +inline LinearRegression::LinearRegression( + const arma::mat& predictors, + const arma::rowvec& responses, + const arma::rowvec& weights, + const double lambda, + const bool intercept) : lambda(lambda), intercept(intercept) { Train(predictors, responses, weights, intercept); } -double LinearRegression::Train(const arma::mat& predictors, - const arma::rowvec& responses, - const bool intercept) +inline double LinearRegression::Train(const arma::mat& predictors, + const arma::rowvec& responses, + const bool intercept) { return Train(predictors, responses, arma::rowvec(), intercept); } -double LinearRegression::Train(const arma::mat& predictors, - const arma::rowvec& responses, - const arma::rowvec& weights, - const bool intercept) +inline double LinearRegression::Train(const arma::mat& predictors, + const arma::rowvec& responses, + const arma::rowvec& weights, + const bool intercept) { this->intercept = intercept; @@ -93,7 +96,8 @@ double LinearRegression::Train(const arma::mat& predictors, return ComputeError(predictors, responses); } -void LinearRegression::Predict(const arma::mat& points, +inline void LinearRegression::Predict( + const arma::mat& points, arma::rowvec& predictions) const { if (intercept) @@ -122,8 +126,9 @@ void LinearRegression::Predict(const arma::mat& points, } } -double LinearRegression::ComputeError(const arma::mat& predictors, - const arma::rowvec& responses) const +inline double LinearRegression::ComputeError( + const arma::mat& predictors, + const arma::rowvec& responses) const { // Sanity check on data. util::CheckSameSizes(predictors, responses, "LinearRegression::Train()"); @@ -160,3 +165,8 @@ double LinearRegression::ComputeError(const arma::mat& predictors, return cost; } + +} // namespace regression +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/local_coordinate_coding/CMakeLists.txt b/src/mlpack/methods/local_coordinate_coding/CMakeLists.txt index 04d8db4479..cedb019596 100644 --- a/src/mlpack/methods/local_coordinate_coding/CMakeLists.txt +++ b/src/mlpack/methods/local_coordinate_coding/CMakeLists.txt @@ -5,7 +5,6 @@ # that you have files in both sections set(SOURCES lcc.hpp - lcc.cpp lcc_impl.hpp ) diff --git a/src/mlpack/methods/local_coordinate_coding/lcc.cpp b/src/mlpack/methods/local_coordinate_coding/lcc.cpp deleted file mode 100644 index c97aca4ed1..0000000000 --- a/src/mlpack/methods/local_coordinate_coding/lcc.cpp +++ /dev/null @@ -1,247 +0,0 @@ -/** - * @file methods/local_coordinate_coding/lcc.cpp - * @author Nishant Mehta - * - * Implementation of Local Coordinate Coding. - * - * 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. - */ -#include "lcc.hpp" -#include - -namespace mlpack { -namespace lcc { - -LocalCoordinateCoding::LocalCoordinateCoding( - const size_t atoms, - const double lambda, - const size_t maxIterations, - const double tolerance) : - atoms(atoms), - lambda(lambda), - maxIterations(maxIterations), - tolerance(tolerance) -{ - // Nothing to do. -} - -void LocalCoordinateCoding::Encode(const arma::mat& data, arma::mat& codes) -{ - arma::mat invSqDists = 1.0 / (repmat(trans(sum(square(dictionary))), 1, - data.n_cols) + repmat(sum(square(data)), atoms, 1) - 2 * trans(dictionary) - * data); - - arma::mat dictGram = trans(dictionary) * dictionary; - arma::mat dictGramTD(dictGram.n_rows, dictGram.n_cols); - - codes.set_size(atoms, data.n_cols); - for (size_t i = 0; i < data.n_cols; ++i) - { - // Report progress. - if ((i % 100) == 0) - { - Log::Debug << "Optimization at point " << i << "." << std::endl; - } - - arma::vec invW = invSqDists.unsafe_col(i); - arma::mat dictPrime = dictionary * diagmat(invW); - - arma::mat dictGramTD = diagmat(invW) * dictGram * diagmat(invW); - - bool useCholesky = false; - regression::LARS lars(useCholesky, dictGramTD, 0.5 * lambda); - - // Run LARS for this point, by making an alias of the point and passing - // that. - arma::vec beta = codes.unsafe_col(i); - arma::rowvec responses = data.unsafe_col(i).t(); - lars.Train(dictPrime, responses, beta, false); - beta %= invW; // Remember, beta is an alias of codes.col(i). - } -} - -void LocalCoordinateCoding::OptimizeDictionary(const arma::mat& data, - const arma::mat& codes, - const arma::uvec& adjacencies) -{ - // Count number of atomic neighbors for each point x^i. - arma::uvec neighborCounts = arma::zeros(data.n_cols, 1); - if (adjacencies.n_elem > 0) - { - // This gets the column index. Intentional integer division. - size_t curPointInd = (size_t) (adjacencies(0) / atoms); - ++neighborCounts(curPointInd); - - size_t nextColIndex = (curPointInd + 1) * atoms; - for (size_t l = 1; l < adjacencies.n_elem; l++) - { - // If l no longer refers to an element in this column, advance the column - // number accordingly. - if (adjacencies(l) >= nextColIndex) - { - curPointInd = (size_t) (adjacencies(l) / atoms); - nextColIndex = (curPointInd + 1) * atoms; - } - - ++neighborCounts(curPointInd); - } - } - - // Build dataPrime := [X x^1 ... x^1 ... x^n ... x^n] - // where each x^i is repeated for the number of neighbors x^i has. - arma::mat dataPrime = arma::zeros(data.n_rows, - data.n_cols + adjacencies.n_elem); - - dataPrime(arma::span::all, arma::span(0, data.n_cols - 1)) = data; - - size_t curCol = data.n_cols; - for (size_t i = 0; i < data.n_cols; ++i) - { - if (neighborCounts(i) > 0) - { - dataPrime(arma::span::all, arma::span(curCol, curCol + neighborCounts(i) - - 1)) = repmat(data.col(i), 1, neighborCounts(i)); - } - curCol += neighborCounts(i); - } - - // Handle the case of inactive atoms (atoms not used in the given coding). - std::vector inactiveAtoms; - for (size_t j = 0; j < atoms; ++j) - if (accu(codes.row(j) != 0) == 0) - inactiveAtoms.push_back(j); - - const size_t nInactiveAtoms = inactiveAtoms.size(); - const size_t nActiveAtoms = atoms - nInactiveAtoms; - - // Efficient construction of codes restricted to active atoms. - arma::mat codesPrime = arma::zeros(nActiveAtoms, data.n_cols + - adjacencies.n_elem); - arma::vec wSquared = arma::ones(data.n_cols + adjacencies.n_elem, 1); - - if (nInactiveAtoms > 0) - { - Log::Warn << "There are " << nInactiveAtoms - << " inactive atoms. They will be re-initialized randomly.\n"; - - // Create matrix holding only active codes. - arma::mat activeCodes; - math::RemoveRows(codes, inactiveAtoms, activeCodes); - - // Create reverse atom lookup for active atoms. - arma::uvec atomReverseLookup(atoms); - size_t inactiveOffset = 0; - for (size_t i = 0; i < atoms; ++i) - { - if (inactiveAtoms[inactiveOffset] == i) - ++inactiveOffset; - else - atomReverseLookup(i - inactiveOffset) = i; - } - - codesPrime(arma::span::all, arma::span(0, data.n_cols - 1)) = activeCodes; - - // Fill the rest of codesPrime. - for (size_t l = 0; l < adjacencies.n_elem; ++l) - { - // Recover the location in the codes matrix that this adjacency refers to. - size_t atomInd = adjacencies(l) % atoms; - size_t pointInd = (size_t) (adjacencies(l) / atoms); - - // Fill matrix. - codesPrime(atomReverseLookup(atomInd), data.n_cols + l) = 1.0; - wSquared(data.n_cols + l) = codes(atomInd, pointInd); - } - } - else - { - // All atoms are active. - codesPrime(arma::span::all, arma::span(0, data.n_cols - 1)) = codes; - - for (size_t l = 0; l < adjacencies.n_elem; ++l) - { - // Recover the location in the codes matrix that this adjacency refers to. - size_t atomInd = adjacencies(l) % atoms; - size_t pointInd = (size_t) (adjacencies(l) / atoms); - - // Fill matrix. - codesPrime(atomInd, data.n_cols + l) = 1.0; - wSquared(data.n_cols + l) = codes(atomInd, pointInd); - } - } - - wSquared.subvec(data.n_cols, wSquared.n_elem - 1) = lambda * - abs(wSquared.subvec(data.n_cols, wSquared.n_elem - 1)); - - // Solve system. - if (nInactiveAtoms == 0) - { - // No inactive atoms. We can solve directly. - arma::mat A = codesPrime * diagmat(wSquared) * trans(codesPrime); - arma::mat B = codesPrime * diagmat(wSquared) * trans(dataPrime); - - dictionary = trans(solve(A, B)); - /* - dictionary = trans(solve(codesPrime * diagmat(wSquared) * trans(codesPrime), - codesPrime * diagmat(wSquared) * trans(dataPrime))); - */ - } - else - { - // Inactive atoms must be reinitialized randomly, so we cannot solve - // directly for the entire dictionary estimate. - arma::mat dictionaryActive = - trans(solve(codesPrime * diagmat(wSquared) * trans(codesPrime), - codesPrime * diagmat(wSquared) * trans(dataPrime))); - - // Update all atoms. - size_t currentInactiveIndex = 0; - for (size_t i = 0; i < atoms; ++i) - { - if (inactiveAtoms[currentInactiveIndex] == i) - { - // This atom is inactive. Reinitialize it randomly. - dictionary.col(i) = (data.col(math::RandInt(data.n_cols)) + - data.col(math::RandInt(data.n_cols)) + - data.col(math::RandInt(data.n_cols))); - - // Now normalize the atom. - dictionary.col(i) /= norm(dictionary.col(i), 2); - - // Increment inactive atom counter. - ++currentInactiveIndex; - } - else - { - // Update estimate. - dictionary.col(i) = dictionaryActive.col(i - currentInactiveIndex); - } - } - } -} - -double LocalCoordinateCoding::Objective(const arma::mat& data, - const arma::mat& codes, - const arma::uvec& adjacencies) const -{ - double weightedL1NormZ = 0; - - for (size_t l = 0; l < adjacencies.n_elem; l++) - { - // Map adjacency back to its location in the codes matrix. - const size_t atomInd = adjacencies(l) % atoms; - const size_t pointInd = (size_t) (adjacencies(l) / atoms); - - weightedL1NormZ += fabs(codes(atomInd, pointInd)) * arma::as_scalar( - arma::sum(arma::square(dictionary.col(atomInd) - data.col(pointInd)))); - } - - double froNormResidual = norm(data - dictionary * codes, "fro"); - return std::pow(froNormResidual, 2.0) + lambda * weightedL1NormZ; -} - -} // namespace lcc -} // namespace mlpack diff --git a/src/mlpack/methods/local_coordinate_coding/lcc.hpp b/src/mlpack/methods/local_coordinate_coding/lcc.hpp index 681253897e..b489fb07f4 100644 --- a/src/mlpack/methods/local_coordinate_coding/lcc.hpp +++ b/src/mlpack/methods/local_coordinate_coding/lcc.hpp @@ -15,11 +15,12 @@ #include #include +#include // Include three simple dictionary initializers from sparse coding. -#include "../sparse_coding/nothing_initializer.hpp" -#include "../sparse_coding/data_dependent_random_initializer.hpp" -#include "../sparse_coding/random_initializer.hpp" +#include +#include +#include namespace mlpack { namespace lcc { diff --git a/src/mlpack/methods/local_coordinate_coding/lcc_impl.hpp b/src/mlpack/methods/local_coordinate_coding/lcc_impl.hpp index a0b02dd49a..54c903db36 100644 --- a/src/mlpack/methods/local_coordinate_coding/lcc_impl.hpp +++ b/src/mlpack/methods/local_coordinate_coding/lcc_impl.hpp @@ -35,6 +35,19 @@ LocalCoordinateCoding::LocalCoordinateCoding( Train(data, initializer); } +inline LocalCoordinateCoding::LocalCoordinateCoding( + const size_t atoms, + const double lambda, + const size_t maxIterations, + const double tolerance) : + atoms(atoms), + lambda(lambda), + maxIterations(maxIterations), + tolerance(tolerance) +{ + // Nothing to do. +} + template double LocalCoordinateCoding::Train( const arma::mat& data, @@ -103,6 +116,224 @@ double LocalCoordinateCoding::Train( return lastObjVal; } +inline void LocalCoordinateCoding::Encode(const arma::mat& data, + arma::mat& codes) +{ + arma::mat invSqDists = 1.0 / (repmat(trans(sum(square(dictionary))), 1, + data.n_cols) + repmat(sum(square(data)), atoms, 1) - 2 * trans(dictionary) + * data); + + arma::mat dictGram = trans(dictionary) * dictionary; + arma::mat dictGramTD(dictGram.n_rows, dictGram.n_cols); + + codes.set_size(atoms, data.n_cols); + for (size_t i = 0; i < data.n_cols; ++i) + { + // Report progress. + if ((i % 100) == 0) + { + Log::Debug << "Optimization at point " << i << "." << std::endl; + } + + arma::vec invW = invSqDists.unsafe_col(i); + arma::mat dictPrime = dictionary * diagmat(invW); + + arma::mat dictGramTD = diagmat(invW) * dictGram * diagmat(invW); + + bool useCholesky = false; + regression::LARS lars(useCholesky, dictGramTD, 0.5 * lambda); + + // Run LARS for this point, by making an alias of the point and passing + // that. + arma::vec beta = codes.unsafe_col(i); + arma::rowvec responses = data.unsafe_col(i).t(); + lars.Train(dictPrime, responses, beta, false); + beta %= invW; // Remember, beta is an alias of codes.col(i). + } +} + +inline void LocalCoordinateCoding::OptimizeDictionary( + const arma::mat& data, + const arma::mat& codes, + const arma::uvec& adjacencies) +{ + // Count number of atomic neighbors for each point x^i. + arma::uvec neighborCounts = arma::zeros(data.n_cols, 1); + if (adjacencies.n_elem > 0) + { + // This gets the column index. Intentional integer division. + size_t curPointInd = (size_t) (adjacencies(0) / atoms); + ++neighborCounts(curPointInd); + + size_t nextColIndex = (curPointInd + 1) * atoms; + for (size_t l = 1; l < adjacencies.n_elem; l++) + { + // If l no longer refers to an element in this column, advance the column + // number accordingly. + if (adjacencies(l) >= nextColIndex) + { + curPointInd = (size_t) (adjacencies(l) / atoms); + nextColIndex = (curPointInd + 1) * atoms; + } + + ++neighborCounts(curPointInd); + } + } + + // Build dataPrime := [X x^1 ... x^1 ... x^n ... x^n] + // where each x^i is repeated for the number of neighbors x^i has. + arma::mat dataPrime = arma::zeros(data.n_rows, + data.n_cols + adjacencies.n_elem); + + dataPrime(arma::span::all, arma::span(0, data.n_cols - 1)) = data; + + size_t curCol = data.n_cols; + for (size_t i = 0; i < data.n_cols; ++i) + { + if (neighborCounts(i) > 0) + { + dataPrime(arma::span::all, arma::span(curCol, curCol + neighborCounts(i) + - 1)) = repmat(data.col(i), 1, neighborCounts(i)); + } + curCol += neighborCounts(i); + } + + // Handle the case of inactive atoms (atoms not used in the given coding). + std::vector inactiveAtoms; + for (size_t j = 0; j < atoms; ++j) + if (accu(codes.row(j) != 0) == 0) + inactiveAtoms.push_back(j); + + const size_t nInactiveAtoms = inactiveAtoms.size(); + const size_t nActiveAtoms = atoms - nInactiveAtoms; + + // Efficient construction of codes restricted to active atoms. + arma::mat codesPrime = arma::zeros(nActiveAtoms, data.n_cols + + adjacencies.n_elem); + arma::vec wSquared = arma::ones(data.n_cols + adjacencies.n_elem, 1); + + if (nInactiveAtoms > 0) + { + Log::Warn << "There are " << nInactiveAtoms + << " inactive atoms. They will be re-initialized randomly.\n"; + + // Create matrix holding only active codes. + arma::mat activeCodes; + math::RemoveRows(codes, inactiveAtoms, activeCodes); + + // Create reverse atom lookup for active atoms. + arma::uvec atomReverseLookup(atoms); + size_t inactiveOffset = 0; + for (size_t i = 0; i < atoms; ++i) + { + if (inactiveAtoms[inactiveOffset] == i) + ++inactiveOffset; + else + atomReverseLookup(i - inactiveOffset) = i; + } + + codesPrime(arma::span::all, arma::span(0, data.n_cols - 1)) = activeCodes; + + // Fill the rest of codesPrime. + for (size_t l = 0; l < adjacencies.n_elem; ++l) + { + // Recover the location in the codes matrix that this adjacency refers to. + size_t atomInd = adjacencies(l) % atoms; + size_t pointInd = (size_t) (adjacencies(l) / atoms); + + // Fill matrix. + codesPrime(atomReverseLookup(atomInd), data.n_cols + l) = 1.0; + wSquared(data.n_cols + l) = codes(atomInd, pointInd); + } + } + else + { + // All atoms are active. + codesPrime(arma::span::all, arma::span(0, data.n_cols - 1)) = codes; + + for (size_t l = 0; l < adjacencies.n_elem; ++l) + { + // Recover the location in the codes matrix that this adjacency refers to. + size_t atomInd = adjacencies(l) % atoms; + size_t pointInd = (size_t) (adjacencies(l) / atoms); + + // Fill matrix. + codesPrime(atomInd, data.n_cols + l) = 1.0; + wSquared(data.n_cols + l) = codes(atomInd, pointInd); + } + } + + wSquared.subvec(data.n_cols, wSquared.n_elem - 1) = lambda * + abs(wSquared.subvec(data.n_cols, wSquared.n_elem - 1)); + + // Solve system. + if (nInactiveAtoms == 0) + { + // No inactive atoms. We can solve directly. + arma::mat A = codesPrime * diagmat(wSquared) * trans(codesPrime); + arma::mat B = codesPrime * diagmat(wSquared) * trans(dataPrime); + + dictionary = trans(solve(A, B)); + /* + dictionary = trans(solve(codesPrime * diagmat(wSquared) * trans(codesPrime), + codesPrime * diagmat(wSquared) * trans(dataPrime))); + */ + } + else + { + // Inactive atoms must be reinitialized randomly, so we cannot solve + // directly for the entire dictionary estimate. + arma::mat dictionaryActive = + trans(solve(codesPrime * diagmat(wSquared) * trans(codesPrime), + codesPrime * diagmat(wSquared) * trans(dataPrime))); + + // Update all atoms. + size_t currentInactiveIndex = 0; + for (size_t i = 0; i < atoms; ++i) + { + if (inactiveAtoms[currentInactiveIndex] == i) + { + // This atom is inactive. Reinitialize it randomly. + dictionary.col(i) = (data.col(math::RandInt(data.n_cols)) + + data.col(math::RandInt(data.n_cols)) + + data.col(math::RandInt(data.n_cols))); + + // Now normalize the atom. + dictionary.col(i) /= norm(dictionary.col(i), 2); + + // Increment inactive atom counter. + ++currentInactiveIndex; + } + else + { + // Update estimate. + dictionary.col(i) = dictionaryActive.col(i - currentInactiveIndex); + } + } + } +} + +inline double LocalCoordinateCoding::Objective( + const arma::mat& data, + const arma::mat& codes, + const arma::uvec& adjacencies) const +{ + double weightedL1NormZ = 0; + + for (size_t l = 0; l < adjacencies.n_elem; l++) + { + // Map adjacency back to its location in the codes matrix. + const size_t atomInd = adjacencies(l) % atoms; + const size_t pointInd = (size_t) (adjacencies(l) / atoms); + + weightedL1NormZ += fabs(codes(atomInd, pointInd)) * arma::as_scalar( + arma::sum(arma::square(dictionary.col(atomInd) - data.col(pointInd)))); + } + + double froNormResidual = norm(data - dictionary * codes, "fro"); + return std::pow(froNormResidual, 2.0) + lambda * weightedL1NormZ; +} + template void LocalCoordinateCoding::serialize(Archive& ar, const uint32_t /* version */) diff --git a/src/mlpack/methods/matrix_completion/CMakeLists.txt b/src/mlpack/methods/matrix_completion/CMakeLists.txt index 818ec34c80..968ce9240f 100644 --- a/src/mlpack/methods/matrix_completion/CMakeLists.txt +++ b/src/mlpack/methods/matrix_completion/CMakeLists.txt @@ -2,7 +2,7 @@ # Anything not in this list will not be compiled into mlpack. set(SOURCES matrix_completion.hpp - matrix_completion.cpp + matrix_completion_impl.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/matrix_completion/matrix_completion.hpp b/src/mlpack/methods/matrix_completion/matrix_completion.hpp index 8023822958..3eae08260e 100644 --- a/src/mlpack/methods/matrix_completion/matrix_completion.hpp +++ b/src/mlpack/methods/matrix_completion/matrix_completion.hpp @@ -145,4 +145,7 @@ class MatrixCompletion } // namespace matrix_completion } // namespace mlpack +// Include implementation. +#include "matrix_completion_impl.hpp" + #endif diff --git a/src/mlpack/methods/matrix_completion/matrix_completion.cpp b/src/mlpack/methods/matrix_completion/matrix_completion_impl.hpp similarity index 65% rename from src/mlpack/methods/matrix_completion/matrix_completion.cpp rename to src/mlpack/methods/matrix_completion/matrix_completion_impl.hpp index 0101760525..d829788cef 100644 --- a/src/mlpack/methods/matrix_completion/matrix_completion.cpp +++ b/src/mlpack/methods/matrix_completion/matrix_completion_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/matrix_completion/matrix_completion.cpp + * @file methods/matrix_completion/matrix_completion_impl.hpp * @author Stephen Tu * * Implementation of MatrixCompletion class. @@ -9,6 +9,8 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_METHODS_MATRIX_COMPLETION_MATRIX_COMPLETION_IMPL_HPP +#define MLPACK_METHODS_MATRIX_COMPLETION_MATRIX_COMPLETION_IMPL_HPP #include "matrix_completion.hpp" #include @@ -16,35 +18,47 @@ namespace mlpack { namespace matrix_completion { -MatrixCompletion::MatrixCompletion(const size_t m, - const size_t n, - const arma::umat& indices, - const arma::vec& values, - const size_t r) : - m(m), n(n), indices(indices), values(values), +inline MatrixCompletion::MatrixCompletion( + const size_t m, + const size_t n, + const arma::umat& indices, + const arma::vec& values, + const size_t r) : + m(m), + n(n), + indices(indices), + values(values), sdp(indices.n_cols, 0, arma::randu(m + n, r)) { CheckValues(); InitSDP(); } -MatrixCompletion::MatrixCompletion(const size_t m, - const size_t n, - const arma::umat& indices, - const arma::vec& values, - const arma::mat& initialPoint) : - m(m), n(n), indices(indices), values(values), +inline MatrixCompletion::MatrixCompletion( + const size_t m, + const size_t n, + const arma::umat& indices, + const arma::vec& values, + const arma::mat& initialPoint) : + m(m), + n(n), + indices(indices), + values(values), sdp(indices.n_cols, 0, initialPoint) { CheckValues(); InitSDP(); } -MatrixCompletion::MatrixCompletion(const size_t m, - const size_t n, - const arma::umat& indices, - const arma::vec& values) : - m(m), n(n), indices(indices), values(values), +inline MatrixCompletion::MatrixCompletion( + const size_t m, + const size_t n, + const arma::umat& indices, + const arma::vec& values) : + m(m), + n(n), + indices(indices), + values(values), sdp(indices.n_cols, 0, arma::randu(m + n, DefaultRank(m, n, indices.n_cols))) { @@ -52,7 +66,7 @@ MatrixCompletion::MatrixCompletion(const size_t m, InitSDP(); } -void MatrixCompletion::CheckValues() +inline void MatrixCompletion::CheckValues() { if (indices.n_rows != 2) { @@ -73,7 +87,7 @@ void MatrixCompletion::CheckValues() } } -void MatrixCompletion::InitSDP() +inline void MatrixCompletion::InitSDP() { sdp.SDP().C().eye(m + n, m + n); sdp.SDP().SparseB() = 2. * values; @@ -86,7 +100,7 @@ void MatrixCompletion::InitSDP() } } -void MatrixCompletion::Recover(arma::mat& recovered) +inline void MatrixCompletion::Recover(arma::mat& recovered) { recovered = sdp.Function().GetInitialPoint(); sdp.Optimize(recovered); @@ -94,9 +108,9 @@ void MatrixCompletion::Recover(arma::mat& recovered) recovered = recovered(arma::span(0, m - 1), arma::span(m, m + n - 1)); } -size_t MatrixCompletion::DefaultRank(const size_t m, - const size_t n, - const size_t p) +inline size_t MatrixCompletion::DefaultRank(const size_t m, + const size_t n, + const size_t p) { // If r = O(sqrt(p)), then we are guaranteed an exact solution. // For more details, see @@ -114,3 +128,5 @@ size_t MatrixCompletion::DefaultRank(const size_t m, } // namespace matrix_completion } // namespace mlpack + +#endif diff --git a/src/mlpack/methods/neighbor_search/CMakeLists.txt b/src/mlpack/methods/neighbor_search/CMakeLists.txt index b20b562d9c..1d7b76a452 100644 --- a/src/mlpack/methods/neighbor_search/CMakeLists.txt +++ b/src/mlpack/methods/neighbor_search/CMakeLists.txt @@ -14,7 +14,7 @@ set(SOURCES sort_policies/furthest_neighbor_sort_impl.hpp typedef.hpp unmap.hpp - unmap.cpp + unmap_impl.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/neighbor_search/unmap.hpp b/src/mlpack/methods/neighbor_search/unmap.hpp index d0165e9005..7b0ec77780 100644 --- a/src/mlpack/methods/neighbor_search/unmap.hpp +++ b/src/mlpack/methods/neighbor_search/unmap.hpp @@ -63,4 +63,7 @@ void Unmap(const arma::Mat& neighbors, } // namespace neighbor } // namespace mlpack +// Include implementation. +#include "unmap_impl.hpp" + #endif diff --git a/src/mlpack/methods/neighbor_search/unmap.cpp b/src/mlpack/methods/neighbor_search/unmap_impl.hpp similarity index 66% rename from src/mlpack/methods/neighbor_search/unmap.cpp rename to src/mlpack/methods/neighbor_search/unmap_impl.hpp index a8efba8386..6d3b52b057 100644 --- a/src/mlpack/methods/neighbor_search/unmap.cpp +++ b/src/mlpack/methods/neighbor_search/unmap_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/neighbor_search/unmap.cpp + * @file methods/neighbor_search/unmap_impl.hpp * @author Ryan Curtin * * Auxiliary function to unmap neighbor search results. @@ -9,19 +9,22 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_METHODS_NEIGHBOR_SEARCH_UNMAP_IMPL_HPP +#define MLPACK_METHODS_NEIGHBOR_SEARCH_UNMAP_IMPL_HPP + #include "unmap.hpp" namespace mlpack { namespace neighbor { // Useful in the dual-tree setting. -void Unmap(const arma::Mat& neighbors, - const arma::mat& distances, - const std::vector& referenceMap, - const std::vector& queryMap, - arma::Mat& neighborsOut, - arma::mat& distancesOut, - const bool squareRoot) +inline void Unmap(const arma::Mat& neighbors, + const arma::mat& distances, + const std::vector& referenceMap, + const std::vector& queryMap, + arma::Mat& neighborsOut, + arma::mat& distancesOut, + const bool squareRoot) { // Set matrices to correct size. neighborsOut.set_size(neighbors.n_rows, neighbors.n_cols); @@ -44,12 +47,12 @@ void Unmap(const arma::Mat& neighbors, } // Useful in the single-tree setting. -void Unmap(const arma::Mat& neighbors, - const arma::mat& distances, - const std::vector& referenceMap, - arma::Mat& neighborsOut, - arma::mat& distancesOut, - const bool squareRoot) +inline void Unmap(const arma::Mat& neighbors, + const arma::mat& distances, + const std::vector& referenceMap, + arma::Mat& neighborsOut, + arma::mat& distancesOut, + const bool squareRoot) { // Set matrices to correct size. neighborsOut.set_size(neighbors.n_rows, neighbors.n_cols); @@ -67,3 +70,5 @@ void Unmap(const arma::Mat& neighbors, } // namespace neighbor } // namespace mlpack + +#endif diff --git a/src/mlpack/methods/quic_svd/CMakeLists.txt b/src/mlpack/methods/quic_svd/CMakeLists.txt index ef7def2f08..ae7a36adc8 100644 --- a/src/mlpack/methods/quic_svd/CMakeLists.txt +++ b/src/mlpack/methods/quic_svd/CMakeLists.txt @@ -2,7 +2,7 @@ # Anything not in this list will not be compiled into mlpack. set(SOURCES quic_svd.hpp - quic_svd.cpp + quic_svd_impl.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/quic_svd/quic_svd.hpp b/src/mlpack/methods/quic_svd/quic_svd.hpp index ce386881f8..7f7f262800 100644 --- a/src/mlpack/methods/quic_svd/quic_svd.hpp +++ b/src/mlpack/methods/quic_svd/quic_svd.hpp @@ -94,4 +94,7 @@ class QUIC_SVD } // namespace svd } // namespace mlpack +// Include implementation. +#include "quic_svd_impl.hpp" + #endif diff --git a/src/mlpack/methods/quic_svd/quic_svd.cpp b/src/mlpack/methods/quic_svd/quic_svd_impl.hpp similarity index 75% rename from src/mlpack/methods/quic_svd/quic_svd.cpp rename to src/mlpack/methods/quic_svd/quic_svd_impl.hpp index 4baf3a8282..b01bf06029 100644 --- a/src/mlpack/methods/quic_svd/quic_svd.cpp +++ b/src/mlpack/methods/quic_svd/quic_svd_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/quic_svd/quic_svd.cpp + * @file methods/quic_svd/quic_svd_impl.hpp * @author Siddharth Agrawal * * An implementation of QUIC-SVD. @@ -9,30 +9,31 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_METHODS_QUIC_SVD_QUIC_SVD_IMPL_HPP +#define MLPACK_METHODS_QUIC_SVD_QUIC_SVD_IMPL_HPP // In case it hasn't been included yet. #include "quic_svd.hpp" -using namespace mlpack::tree; - namespace mlpack { namespace svd { -QUIC_SVD::QUIC_SVD(const arma::mat& dataset, - arma::mat& u, - arma::mat& v, - arma::mat& sigma, - const double epsilon, - const double delta) : +inline QUIC_SVD::QUIC_SVD( + const arma::mat& dataset, + arma::mat& u, + arma::mat& v, + arma::mat& sigma, + const double epsilon, + const double delta) : dataset(dataset) { // Since columns are sample in the implementation, the matrix is transposed if // necessary for maximum speedup. - CosineTree* ctree; + tree::CosineTree* ctree; if (dataset.n_cols > dataset.n_rows) - ctree = new CosineTree(dataset, epsilon, delta); + ctree = new tree::CosineTree(dataset, epsilon, delta); else - ctree = new CosineTree(dataset.t(), epsilon, delta); + ctree = new tree::CosineTree(dataset.t(), epsilon, delta); // Get subspace basis by creating the cosine tree. ctree->GetFinalBasis(basis); @@ -45,9 +46,9 @@ QUIC_SVD::QUIC_SVD(const arma::mat& dataset, ExtractSVD(u, v, sigma); } -void QUIC_SVD::ExtractSVD(arma::mat& u, - arma::mat& v, - arma::mat& sigma) +inline void QUIC_SVD::ExtractSVD(arma::mat& u, + arma::mat& v, + arma::mat& sigma) { // Calculate A * V_hat, necessary for further calculations. arma::mat projectedMat; @@ -82,3 +83,5 @@ void QUIC_SVD::ExtractSVD(arma::mat& u, } // namespace svd } // namespace mlpack + +#endif diff --git a/src/mlpack/methods/radical/CMakeLists.txt b/src/mlpack/methods/radical/CMakeLists.txt index f51f2c377d..79968372cd 100644 --- a/src/mlpack/methods/radical/CMakeLists.txt +++ b/src/mlpack/methods/radical/CMakeLists.txt @@ -2,7 +2,7 @@ # Anything not in this list will not be compiled into the output library set(SOURCES radical.hpp - radical.cpp + radical_impl.hpp ) # add directory name to sources diff --git a/src/mlpack/methods/radical/radical.hpp b/src/mlpack/methods/radical/radical.hpp index 94d30faee2..5d79b3daad 100644 --- a/src/mlpack/methods/radical/radical.hpp +++ b/src/mlpack/methods/radical/radical.hpp @@ -16,6 +16,8 @@ #include #include +#include +#include namespace mlpack { namespace radical { @@ -148,4 +150,7 @@ void WhitenFeatureMajorMatrix(const arma::mat& matX, } // namespace radical } // namespace mlpack +// Include implementation. +#include "radical_impl.hpp" + #endif diff --git a/src/mlpack/methods/radical/radical.cpp b/src/mlpack/methods/radical/radical_impl.hpp similarity index 71% rename from src/mlpack/methods/radical/radical.cpp rename to src/mlpack/methods/radical/radical_impl.hpp index 481fb0f0fc..6a492e0972 100644 --- a/src/mlpack/methods/radical/radical.cpp +++ b/src/mlpack/methods/radical/radical_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/radical/radical.cpp + * @file methods/radical/radical_impl.hpp * @author Nishant Mehta * * Implementation of Radical class @@ -9,22 +9,21 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_METHODS_RADICAL_RADICAL_IMPL_HPP +#define MLPACK_METHODS_RADICAL_RADICAL_IMPL_HPP #include "radical.hpp" -#include -#include -using namespace std; -using namespace arma; -using namespace mlpack; -using namespace mlpack::radical; +namespace mlpack { +namespace radical { // Set the parameters to RADICAL. -Radical::Radical(const double noiseStdDev, - const size_t replicates, - const size_t angles, - const size_t sweeps, - const size_t m) : +inline Radical::Radical( + const double noiseStdDev, + const size_t replicates, + const size_t angles, + const size_t sweeps, + const size_t m) : noiseStdDev(noiseStdDev), replicates(replicates), angles(angles), @@ -34,14 +33,15 @@ Radical::Radical(const double noiseStdDev, // Nothing to do here. } -void Radical::CopyAndPerturb(mat& xNew, const mat& x) const +inline void Radical::CopyAndPerturb(arma::mat& xNew, + const arma::mat& x) const { - xNew = repmat(x, replicates, 1) + noiseStdDev * randn(replicates * x.n_rows, + xNew = arma::repmat(x, replicates, 1) + noiseStdDev * arma::randn(replicates * x.n_rows, x.n_cols); } -double Radical::Vasicek(vec& z) const +inline double Radical::Vasicek(arma::vec& z) const { z = sort(z); @@ -54,25 +54,26 @@ double Radical::Vasicek(vec& z) const // Apparently faster. double sum = 0; - uword range = z.n_elem - m; - for (uword i = 0; i < range; ++i) + arma::uword range = z.n_elem - m; + for (arma::uword i = 0; i < range; ++i) { - sum += log(max(z(i + m) - z(i), DBL_MIN)); + sum += log(std::max(z(i + m) - z(i), DBL_MIN)); } return sum; } -double Radical::DoRadical2D(const mat& matX, util::Timers& timers) +inline double Radical::DoRadical2D(const arma::mat& matX, + util::Timers& timers) { timers.Start("radical_copy_and_perturb"); CopyAndPerturb(perturbed, matX); timers.Stop("radical_copy_and_perturb"); - mat::fixed<2, 2> matJacobi; + arma::mat::fixed<2, 2> matJacobi; - vec values(angles); + arma::vec values(angles); for (size_t i = 0; i < angles; ++i) { @@ -86,29 +87,29 @@ double Radical::DoRadical2D(const mat& matX, util::Timers& timers) matJacobi(1, 1) = cosTheta; candidate = perturbed * matJacobi; - vec candidateY1 = candidate.unsafe_col(0); - vec candidateY2 = candidate.unsafe_col(1); + arma::vec candidateY1 = candidate.unsafe_col(0); + arma::vec candidateY2 = candidate.unsafe_col(1); values(i) = Vasicek(candidateY1) + Vasicek(candidateY2); } - uword indOpt = 0; + arma::uword indOpt = 0; values.min(indOpt); // we ignore the return value; we don't care about it return (indOpt / (double) angles) * M_PI / 2.0; } -void Radical::DoRadical(const mat& matXT, - mat& matY, - mat& matW, - util::Timers& timers) +inline void Radical::DoRadical(const arma::mat& matXT, + arma::mat& matY, + arma::mat& matW, + util::Timers& timers) { // matX is nPoints by nDims (although less intuitive than columns being // points, and although this is the transpose of the ICA literature, this // choice is for computational efficiency when repeatedly generating // two-dimensional coordinate projections for Radical2D). timers.Start("radical_transpose_data"); - mat matX = trans(matXT); + arma::mat matX = trans(matXT); timers.Stop("radical_transpose_data"); // If m was not specified, initialize m as recommended in @@ -120,8 +121,8 @@ void Radical::DoRadical(const mat& matXT, const size_t nPoints = matX.n_rows; timers.Start("radical_whiten_data"); - mat matXWhitened; - mat matWhitening; + arma::mat matXWhitened; + arma::mat matWhitening; WhitenFeatureMajorMatrix(matX, matY, matWhitening); timers.Stop("radical_whiten_data"); // matY is now the whitened form of matX. @@ -135,9 +136,9 @@ void Radical::DoRadical(const mat& matXT, timers.Start("radical_do_radical"); matW = matWhitening; - mat matYSubspace(nPoints, 2); + arma::mat matYSubspace(nPoints, 2); - mat matJ = eye(nDims, nDims); + arma::mat matJ = arma::eye(nDims, nDims); for (size_t sweepNum = 0; sweepNum < sweeps; sweepNum++) { @@ -184,13 +185,18 @@ void Radical::DoRadical(const mat& matXT, timers.Stop("radical_transpose_data"); } -void mlpack::radical::WhitenFeatureMajorMatrix(const mat& matX, - mat& matXWhitened, - mat& matWhitening) +inline void WhitenFeatureMajorMatrix(const arma::mat& matX, + arma::mat& matXWhitened, + arma::mat& matWhitening) { - mat matU, matV; - vec s; + arma::mat matU, matV; + arma::vec s; arma::svd(matU, s, matV, cov(matX)); matWhitening = matU * diagmat(1 / sqrt(s)) * trans(matV); matXWhitened = matX * matWhitening; } + +} // namespace radical +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/randomized_svd/CMakeLists.txt b/src/mlpack/methods/randomized_svd/CMakeLists.txt index ad0e5b34fa..542b55e255 100644 --- a/src/mlpack/methods/randomized_svd/CMakeLists.txt +++ b/src/mlpack/methods/randomized_svd/CMakeLists.txt @@ -2,7 +2,7 @@ # Anything not in this list will not be compiled into mlpack. set(SOURCES randomized_svd.hpp - randomized_svd.cpp + randomized_svd_impl.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/randomized_svd/randomized_svd.hpp b/src/mlpack/methods/randomized_svd/randomized_svd.hpp index 574ba1cd66..5949bb1487 100644 --- a/src/mlpack/methods/randomized_svd/randomized_svd.hpp +++ b/src/mlpack/methods/randomized_svd/randomized_svd.hpp @@ -260,4 +260,7 @@ class RandomizedSVD } // namespace svd } // namespace mlpack +// Include implementation. +#include "randomized_svd_impl.hpp" + #endif diff --git a/src/mlpack/methods/randomized_svd/randomized_svd.cpp b/src/mlpack/methods/randomized_svd/randomized_svd_impl.hpp similarity index 51% rename from src/mlpack/methods/randomized_svd/randomized_svd.cpp rename to src/mlpack/methods/randomized_svd/randomized_svd_impl.hpp index f812277cf8..ce813353d0 100644 --- a/src/mlpack/methods/randomized_svd/randomized_svd.cpp +++ b/src/mlpack/methods/randomized_svd/randomized_svd_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/randomized_svd/randomized_svd.cpp + * @file methods/randomized_svd/randomized_svd_impl.hpp * @author Marcus Edel * * Implementation of the randomized SVD method. @@ -10,19 +10,23 @@ * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_METHODS_RANDOMIZED_SVD_RANDOMIZED_SVD_IMPL_HPP +#define MLPACK_METHODS_RANDOMIZED_SVD_RANDOMIZED_SVD_IMPL_HPP + #include "randomized_svd.hpp" namespace mlpack { namespace svd { -RandomizedSVD::RandomizedSVD(const arma::mat& data, - arma::mat& u, - arma::vec& s, - arma::mat& v, - const size_t iteratedPower, - const size_t maxIterations, - const size_t rank, - const double eps) : +inline RandomizedSVD::RandomizedSVD( + const arma::mat& data, + arma::mat& u, + arma::vec& s, + arma::mat& v, + const size_t iteratedPower, + const size_t maxIterations, + const size_t rank, + const double eps) : iteratedPower(iteratedPower), maxIterations(maxIterations), eps(eps) @@ -37,9 +41,10 @@ RandomizedSVD::RandomizedSVD(const arma::mat& data, } } -RandomizedSVD::RandomizedSVD(const size_t iteratedPower, - const size_t maxIterations, - const double eps) : +inline RandomizedSVD::RandomizedSVD( + const size_t iteratedPower, + const size_t maxIterations, + const double eps) : iteratedPower(iteratedPower), maxIterations(maxIterations), eps(eps) @@ -48,11 +53,11 @@ RandomizedSVD::RandomizedSVD(const size_t iteratedPower, } -void RandomizedSVD::Apply(const arma::sp_mat& data, - arma::mat& u, - arma::vec& s, - arma::mat& v, - const size_t rank) +inline void RandomizedSVD::Apply(const arma::sp_mat& data, + arma::mat& u, + arma::vec& s, + arma::mat& v, + const size_t rank) { // Center the data into a temporary matrix for sparse matrix. arma::sp_mat rowMean = arma::sum(data, 1) / data.n_cols; @@ -60,11 +65,11 @@ void RandomizedSVD::Apply(const arma::sp_mat& data, Apply(data, u, s, v, rank, rowMean); } -void RandomizedSVD::Apply(const arma::mat& data, - arma::mat& u, - arma::vec& s, - arma::mat& v, - const size_t rank) +inline void RandomizedSVD::Apply(const arma::mat& data, + arma::mat& u, + arma::vec& s, + arma::mat& v, + const size_t rank) { // Center the data into a temporary matrix. arma::mat rowMean = arma::sum(data, 1) / data.n_cols + eps; @@ -74,3 +79,5 @@ void RandomizedSVD::Apply(const arma::mat& data, } // namespace svd } // namespace mlpack + +#endif diff --git a/src/mlpack/methods/range_search/CMakeLists.txt b/src/mlpack/methods/range_search/CMakeLists.txt index 7bff7bf0e4..c7f8f8ecef 100644 --- a/src/mlpack/methods/range_search/CMakeLists.txt +++ b/src/mlpack/methods/range_search/CMakeLists.txt @@ -8,7 +8,6 @@ set(SOURCES range_search_stat.hpp rs_model.hpp rs_model_impl.hpp - rs_model.cpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/range_search/rs_model.cpp b/src/mlpack/methods/range_search/rs_model.cpp deleted file mode 100644 index 52fe3bea62..0000000000 --- a/src/mlpack/methods/range_search/rs_model.cpp +++ /dev/null @@ -1,289 +0,0 @@ -/** - * @file methods/range_search/rs_model.cpp - * @author Ryan Curtin - * - * Implementation of serialize() and inline functions for RSModel. - * - * 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. - */ -#include "rs_model.hpp" - -#include - -namespace mlpack { -namespace range { - -/** - * Initialize the RSModel with the given tree type and whether or not a random - * basis should be used. - */ -RSModel::RSModel(TreeTypes treeType, bool randomBasis) : - treeType(treeType), - leafSize(0), - randomBasis(randomBasis), - rSearch(NULL) -{ - // Nothing to do. -} - -// Copy constructor. -RSModel::RSModel(const RSModel& other) : - treeType(other.treeType), - leafSize(other.leafSize), - randomBasis(other.randomBasis), - q(other.q), - rSearch(other.rSearch->Clone()) -{ - // Nothing to do. -} - -// Move constructor. -RSModel::RSModel(RSModel&& other) : - treeType(other.treeType), - leafSize(other.leafSize), - randomBasis(other.randomBasis), - q(std::move(other.q)), - rSearch(std::move(other.rSearch)) -{ - // Reset other model. - other.treeType = TreeTypes::KD_TREE; - other.leafSize = 0; - other.randomBasis = false; -} - -// Copy operator. -RSModel& RSModel::operator=(const RSModel& other) -{ - if (this != &other) - { - delete rSearch; - - treeType = other.treeType; - leafSize = other.leafSize; - randomBasis = other.randomBasis; - q = other.q; - rSearch = other.rSearch->Clone(); - } - - return *this; -} - -// Move operator. -RSModel& RSModel::operator=(RSModel&& other) -{ - if (this != &other) - { - delete rSearch; - - treeType = other.treeType; - leafSize = other.leafSize; - randomBasis = other.randomBasis; - q = std::move(other.q); - rSearch = std::move(other.rSearch); - - other.treeType = TreeTypes::KD_TREE; - other.leafSize = 0; - other.randomBasis = false; - } - - return *this; -} - -// Clean memory, if necessary. -RSModel::~RSModel() -{ - delete rSearch; -} - -void RSModel::InitializeModel(const bool naive, const bool singleMode) -{ - // Clean memory, if necessary. - delete rSearch; - - switch (treeType) - { - case KD_TREE: - rSearch = new LeafSizeRSWrapper(naive, singleMode); - break; - - case COVER_TREE: - rSearch = new RSWrapper(naive, singleMode); - break; - - case R_TREE: - rSearch = new RSWrapper(naive, singleMode); - break; - - case R_STAR_TREE: - rSearch = new RSWrapper(naive, singleMode); - break; - - case BALL_TREE: - rSearch = new LeafSizeRSWrapper(naive, singleMode); - break; - - case X_TREE: - rSearch = new RSWrapper(naive, singleMode); - break; - - case HILBERT_R_TREE: - rSearch = new RSWrapper(naive, singleMode); - break; - - case R_PLUS_TREE: - rSearch = new RSWrapper(naive, singleMode); - break; - - case R_PLUS_PLUS_TREE: - rSearch = new RSWrapper(naive, singleMode); - break; - - case VP_TREE: - rSearch = new LeafSizeRSWrapper(naive, singleMode); - break; - - case RP_TREE: - rSearch = new LeafSizeRSWrapper(naive, singleMode); - break; - - case MAX_RP_TREE: - rSearch = new LeafSizeRSWrapper(naive, singleMode); - break; - - case UB_TREE: - rSearch = new LeafSizeRSWrapper(naive, singleMode); - break; - - case OCTREE: - rSearch = new LeafSizeRSWrapper(naive, singleMode); - break; - } -} - -void RSModel::BuildModel(util::Timers& timers, - arma::mat&& referenceSet, - const size_t leafSize, - const bool naive, - const bool singleMode) -{ - // Initialize random basis if necessary. - if (randomBasis) - { - timers.Start("computing_random_basis"); - Log::Info << "Creating random basis..." << std::endl; - math::RandomBasis(q, referenceSet.n_rows); - - // Do we need to modify the reference set? - if (randomBasis) - referenceSet = q * referenceSet; - timers.Stop("computing_random_basis"); - } - - this->leafSize = leafSize; - - if (!naive) - Log::Info << "Building reference tree..." << std::endl; - - InitializeModel(naive, singleMode); - - rSearch->Train(timers, std::move(referenceSet), leafSize); - - if (!naive) - Log::Info << "Tree built." << std::endl; -} - -// Perform range search. -void RSModel::Search(util::Timers& timers, - arma::mat&& querySet, - const math::Range& range, - std::vector>& neighbors, - std::vector>& distances) -{ - // We may need to map the query set randomly. - if (randomBasis) - { - timers.Start("applying_random_basis"); - querySet = q * querySet; - timers.Stop("applying_random_basis"); - } - - Log::Info << "Search for points in the range [" << range.Lo() << ", " - << range.Hi() << "] with "; - if (!Naive() && !SingleMode()) - Log::Info << "dual-tree " << TreeName() << " search..." << std::endl; - else if (!Naive()) - Log::Info << "single-tree " << TreeName() << " search..." << std::endl; - else - Log::Info << "brute-force (naive) search..." << std::endl; - - rSearch->Search(timers, std::move(querySet), range, neighbors, distances, - leafSize); -} - -// Perform range search (monochromatic case). -void RSModel::Search(util::Timers& timers, - const math::Range& range, - std::vector>& neighbors, - std::vector>& distances) -{ - Log::Info << "Search for points in the range [" << range.Lo() << ", " - << range.Hi() << "] with "; - if (!Naive() && !SingleMode()) - Log::Info << "dual-tree " << TreeName() << " search..." << std::endl; - else if (!Naive()) - Log::Info << "single-tree " << TreeName() << " search..." << std::endl; - else - Log::Info << "brute-force (naive) search..." << std::endl; - - rSearch->Search(timers, range, neighbors, distances); -} - -// Get the name of the tree type. -std::string RSModel::TreeName() const -{ - switch (treeType) - { - case KD_TREE: - return "kd-tree"; - case COVER_TREE: - return "cover tree"; - case R_TREE: - return "R tree"; - case R_STAR_TREE: - return "R* tree"; - case BALL_TREE: - return "ball tree"; - case X_TREE: - return "X tree"; - case HILBERT_R_TREE: - return "Hilbert R tree"; - case R_PLUS_TREE: - return "R+ tree"; - case R_PLUS_PLUS_TREE: - return "R++ tree"; - case VP_TREE: - return "vantage point tree"; - case RP_TREE: - return "random projection tree (mean split)"; - case MAX_RP_TREE: - return "random projection tree (max split)"; - case UB_TREE: - return "UB tree"; - case OCTREE: - return "octree"; - default: - return "unknown tree"; - } -} - -// Clean memory. -void RSModel::CleanMemory() -{ - delete rSearch; -} - -} // namespace range -} // namespace mlpack diff --git a/src/mlpack/methods/range_search/rs_model_impl.hpp b/src/mlpack/methods/range_search/rs_model_impl.hpp index 8eaedf4e36..c55cf0fee6 100644 --- a/src/mlpack/methods/range_search/rs_model_impl.hpp +++ b/src/mlpack/methods/range_search/rs_model_impl.hpp @@ -20,6 +20,276 @@ namespace mlpack { namespace range { +/** + * Initialize the RSModel with the given tree type and whether or not a random + * basis should be used. + */ +inline RSModel::RSModel(TreeTypes treeType, bool randomBasis) : + treeType(treeType), + leafSize(0), + randomBasis(randomBasis), + rSearch(NULL) +{ + // Nothing to do. +} + +// Copy constructor. +inline RSModel::RSModel(const RSModel& other) : + treeType(other.treeType), + leafSize(other.leafSize), + randomBasis(other.randomBasis), + q(other.q), + rSearch(other.rSearch->Clone()) +{ + // Nothing to do. +} + +// Move constructor. +inline RSModel::RSModel(RSModel&& other) : + treeType(other.treeType), + leafSize(other.leafSize), + randomBasis(other.randomBasis), + q(std::move(other.q)), + rSearch(std::move(other.rSearch)) +{ + // Reset other model. + other.treeType = TreeTypes::KD_TREE; + other.leafSize = 0; + other.randomBasis = false; +} + +// Copy operator. +inline RSModel& RSModel::operator=(const RSModel& other) +{ + if (this != &other) + { + delete rSearch; + + treeType = other.treeType; + leafSize = other.leafSize; + randomBasis = other.randomBasis; + q = other.q; + rSearch = other.rSearch->Clone(); + } + + return *this; +} + +// Move operator. +inline RSModel& RSModel::operator=(RSModel&& other) +{ + if (this != &other) + { + delete rSearch; + + treeType = other.treeType; + leafSize = other.leafSize; + randomBasis = other.randomBasis; + q = std::move(other.q); + rSearch = std::move(other.rSearch); + + other.treeType = TreeTypes::KD_TREE; + other.leafSize = 0; + other.randomBasis = false; + } + + return *this; +} + +// Clean memory, if necessary. +inline RSModel::~RSModel() +{ + delete rSearch; +} + +inline void RSModel::InitializeModel(const bool naive, + const bool singleMode) +{ + // Clean memory, if necessary. + delete rSearch; + + switch (treeType) + { + case KD_TREE: + rSearch = new LeafSizeRSWrapper(naive, singleMode); + break; + + case COVER_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case R_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case R_STAR_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case BALL_TREE: + rSearch = new LeafSizeRSWrapper(naive, singleMode); + break; + + case X_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case HILBERT_R_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case R_PLUS_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case R_PLUS_PLUS_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case VP_TREE: + rSearch = new LeafSizeRSWrapper(naive, singleMode); + break; + + case RP_TREE: + rSearch = new LeafSizeRSWrapper(naive, singleMode); + break; + + case MAX_RP_TREE: + rSearch = new LeafSizeRSWrapper(naive, singleMode); + break; + + case UB_TREE: + rSearch = new LeafSizeRSWrapper(naive, singleMode); + break; + + case OCTREE: + rSearch = new LeafSizeRSWrapper(naive, singleMode); + break; + } +} + +inline void RSModel::BuildModel(util::Timers& timers, + arma::mat&& referenceSet, + const size_t leafSize, + const bool naive, + const bool singleMode) +{ + // Initialize random basis if necessary. + if (randomBasis) + { + timers.Start("computing_random_basis"); + Log::Info << "Creating random basis..." << std::endl; + math::RandomBasis(q, referenceSet.n_rows); + + // Do we need to modify the reference set? + if (randomBasis) + referenceSet = q * referenceSet; + timers.Stop("computing_random_basis"); + } + + this->leafSize = leafSize; + + if (!naive) + Log::Info << "Building reference tree..." << std::endl; + + InitializeModel(naive, singleMode); + + rSearch->Train(timers, std::move(referenceSet), leafSize); + + if (!naive) + Log::Info << "Tree built." << std::endl; +} + +// Perform range search. +inline void RSModel::Search(util::Timers& timers, + arma::mat&& querySet, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances) +{ + // We may need to map the query set randomly. + if (randomBasis) + { + timers.Start("applying_random_basis"); + querySet = q * querySet; + timers.Stop("applying_random_basis"); + } + + Log::Info << "Search for points in the range [" << range.Lo() << ", " + << range.Hi() << "] with "; + if (!Naive() && !SingleMode()) + Log::Info << "dual-tree " << TreeName() << " search..." << std::endl; + else if (!Naive()) + Log::Info << "single-tree " << TreeName() << " search..." << std::endl; + else + Log::Info << "brute-force (naive) search..." << std::endl; + + rSearch->Search(timers, std::move(querySet), range, neighbors, distances, + leafSize); +} + +// Perform range search (monochromatic case). +inline void RSModel::Search(util::Timers& timers, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances) +{ + Log::Info << "Search for points in the range [" << range.Lo() << ", " + << range.Hi() << "] with "; + if (!Naive() && !SingleMode()) + Log::Info << "dual-tree " << TreeName() << " search..." << std::endl; + else if (!Naive()) + Log::Info << "single-tree " << TreeName() << " search..." << std::endl; + else + Log::Info << "brute-force (naive) search..." << std::endl; + + rSearch->Search(timers, range, neighbors, distances); +} + +// Get the name of the tree type. +inline std::string RSModel::TreeName() const +{ + switch (treeType) + { + case KD_TREE: + return "kd-tree"; + case COVER_TREE: + return "cover tree"; + case R_TREE: + return "R tree"; + case R_STAR_TREE: + return "R* tree"; + case BALL_TREE: + return "ball tree"; + case X_TREE: + return "X tree"; + case HILBERT_R_TREE: + return "Hilbert R tree"; + case R_PLUS_TREE: + return "R+ tree"; + case R_PLUS_PLUS_TREE: + return "R++ tree"; + case VP_TREE: + return "vantage point tree"; + case RP_TREE: + return "random projection tree (mean split)"; + case MAX_RP_TREE: + return "random projection tree (max split)"; + case UB_TREE: + return "UB tree"; + case OCTREE: + return "octree"; + default: + return "unknown tree"; + } +} + +// Clean memory. +inline void RSModel::CleanMemory() +{ + delete rSearch; +} + template class TreeType> diff --git a/src/mlpack/methods/rann/CMakeLists.txt b/src/mlpack/methods/rann/CMakeLists.txt index a3eb4ff2e2..592da747e7 100644 --- a/src/mlpack/methods/rann/CMakeLists.txt +++ b/src/mlpack/methods/rann/CMakeLists.txt @@ -18,12 +18,11 @@ set(SOURCES # utilities ra_util.hpp - ra_util.cpp + ra_util_impl.hpp # model ra_model.hpp ra_model_impl.hpp - ra_model.cpp ) # add directory name to sources diff --git a/src/mlpack/methods/rann/ra_model.cpp b/src/mlpack/methods/rann/ra_model.cpp deleted file mode 100644 index 061bece78a..0000000000 --- a/src/mlpack/methods/rann/ra_model.cpp +++ /dev/null @@ -1,238 +0,0 @@ -/** - * @file methods/rann/ra_model.cpp - * @author Ryan Curtin - * - * Implementation of the RAModel class. - * - * 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. - */ -#include "ra_model.hpp" -#include - -namespace mlpack { -namespace neighbor { - -RAModel::RAModel(const TreeTypes treeType, const bool randomBasis) : - treeType(treeType), - leafSize(20), - randomBasis(randomBasis), - raSearch(NULL) -{ - // Nothing to do. -} - -// Copy constructor. -RAModel::RAModel(const RAModel& other) : - treeType(other.treeType), - leafSize(other.leafSize), - randomBasis(other.randomBasis), - q(other.q), - raSearch(other.raSearch->Clone()) -{ - // Nothing to do. -} - -// Move constructor. -RAModel::RAModel(RAModel&& other) : - treeType(other.treeType), - leafSize(other.leafSize), - randomBasis(other.randomBasis), - q(std::move(other.q)), - raSearch(std::move(other.raSearch)) -{ - // Clear other model. - other.treeType = TreeTypes::KD_TREE; - other.leafSize = 20; - other.randomBasis = false; -} - -// Copy operator. -RAModel& RAModel::operator=(const RAModel& other) -{ - if (this != &other) - { - // Clear current model. - delete raSearch; - - treeType = other.treeType; - leafSize = other.leafSize; - randomBasis = other.randomBasis; - q = other.q; - raSearch = other.raSearch->Clone(); - } - - return *this; -} - -RAModel& RAModel::operator=(RAModel&& other) -{ - if (this != &other) - { - // Clear current model. - delete raSearch; - - treeType = other.treeType; - leafSize = other.leafSize; - randomBasis = other.randomBasis; - q = std::move(other.q); - raSearch = std::move(other.raSearch); - - // Reset other model. - other.treeType = TreeTypes::KD_TREE; - other.leafSize = 20; - other.randomBasis = false; - } - - return *this; -} - -// Clean memory, if necessary -RAModel::~RAModel() -{ - delete raSearch; -} - -void RAModel::InitializeModel(const bool naive, const bool singleMode) -{ - // Clean memory, if necessary. - delete raSearch; - - switch (treeType) - { - case KD_TREE: - raSearch = new LeafSizeRAWrapper(naive, singleMode); - break; - case COVER_TREE: - raSearch = new RAWrapper(naive, singleMode); - break; - case R_TREE: - raSearch = new RAWrapper(naive, singleMode); - break; - case R_STAR_TREE: - raSearch = new RAWrapper(naive, singleMode); - break; - case X_TREE: - raSearch = new RAWrapper(naive, singleMode); - break; - case HILBERT_R_TREE: - raSearch = new RAWrapper(naive, singleMode); - break; - case R_PLUS_TREE: - raSearch = new RAWrapper(naive, singleMode); - break; - case R_PLUS_PLUS_TREE: - raSearch = new RAWrapper(naive, singleMode); - break; - case UB_TREE: - raSearch = new LeafSizeRAWrapper(naive, singleMode); - break; - case OCTREE: - raSearch = new LeafSizeRAWrapper(naive, singleMode); - break; - } -} - -void RAModel::BuildModel(util::Timers& timers, - arma::mat&& referenceSet, - const size_t leafSize, - const bool naive, - const bool singleMode) -{ - // Initialize random basis, if necessary. - if (randomBasis) - { - timers.Start("computing_random_basis"); - Log::Info << "Creating random basis..." << std::endl; - math::RandomBasis(q, referenceSet.n_rows); - - referenceSet = q * referenceSet; - timers.Stop("computing_random_basis"); - } - - this->leafSize = leafSize; - - if (!naive) - Log::Info << "Building reference tree..." << std::endl; - - InitializeModel(naive, singleMode); - - raSearch->Train(timers, std::move(referenceSet), leafSize); - - if (!naive) - Log::Info << "Tree built." << std::endl; -} - -void RAModel::Search(util::Timers& timers, - arma::mat&& querySet, - const size_t k, - arma::Mat& neighbors, - arma::mat& distances) -{ - // Apply the random basis if necessary. - if (randomBasis) - querySet = q * querySet; - - Log::Info << "Searching for " << k << " approximate nearest neighbors with "; - if (!Naive() && !SingleMode()) - Log::Info << "dual-tree rank-approximate " << TreeName() << " search..."; - else if (!Naive()) - Log::Info << "single-tree rank-approximate " << TreeName() << " search..."; - else - Log::Info << "brute-force (naive) rank-approximate search..."; - Log::Info << std::endl; - - raSearch->Search(timers, std::move(querySet), k, neighbors, distances, - leafSize); -} - -void RAModel::Search(util::Timers& timers, - const size_t k, - arma::Mat& neighbors, - arma::mat& distances) -{ - Log::Info << "Searching for " << k << " approximate nearest neighbors with "; - if (!Naive() && !SingleMode()) - Log::Info << "dual-tree rank-approximate " << TreeName() << " search..."; - else if (!Naive()) - Log::Info << "single-tree rank-approximate " << TreeName() << " search..."; - else - Log::Info << "brute-force (naive) rank-approximate search..."; - Log::Info << std::endl; - - raSearch->Search(timers, k, neighbors, distances); -} - -std::string RAModel::TreeName() const -{ - switch (treeType) - { - case KD_TREE: - return "kd-tree"; - case COVER_TREE: - return "cover tree"; - case R_TREE: - return "R tree"; - case R_STAR_TREE: - return "R* tree"; - case X_TREE: - return "X tree"; - case HILBERT_R_TREE: - return "Hilbert R tree"; - case R_PLUS_TREE: - return "R+ tree"; - case R_PLUS_PLUS_TREE: - return "R++ tree"; - case UB_TREE: - return "UB tree"; - case OCTREE: - return "octree"; - default: - return "unknown tree"; - } -} - -} // namespace neighbor -} // namespace mlpack diff --git a/src/mlpack/methods/rann/ra_model_impl.hpp b/src/mlpack/methods/rann/ra_model_impl.hpp index 088f181138..a5c38004a7 100644 --- a/src/mlpack/methods/rann/ra_model_impl.hpp +++ b/src/mlpack/methods/rann/ra_model_impl.hpp @@ -19,6 +19,226 @@ namespace mlpack { namespace neighbor { +inline RAModel::RAModel(const TreeTypes treeType, const bool randomBasis) : + treeType(treeType), + leafSize(20), + randomBasis(randomBasis), + raSearch(NULL) +{ + // Nothing to do. +} + +// Copy constructor. +inline RAModel::RAModel(const RAModel& other) : + treeType(other.treeType), + leafSize(other.leafSize), + randomBasis(other.randomBasis), + q(other.q), + raSearch(other.raSearch->Clone()) +{ + // Nothing to do. +} + +// Move constructor. +inline RAModel::RAModel(RAModel&& other) : + treeType(other.treeType), + leafSize(other.leafSize), + randomBasis(other.randomBasis), + q(std::move(other.q)), + raSearch(std::move(other.raSearch)) +{ + // Clear other model. + other.treeType = TreeTypes::KD_TREE; + other.leafSize = 20; + other.randomBasis = false; +} + +// Copy operator. +inline RAModel& RAModel::operator=(const RAModel& other) +{ + if (this != &other) + { + // Clear current model. + delete raSearch; + + treeType = other.treeType; + leafSize = other.leafSize; + randomBasis = other.randomBasis; + q = other.q; + raSearch = other.raSearch->Clone(); + } + + return *this; +} + +inline RAModel& RAModel::operator=(RAModel&& other) +{ + if (this != &other) + { + // Clear current model. + delete raSearch; + + treeType = other.treeType; + leafSize = other.leafSize; + randomBasis = other.randomBasis; + q = std::move(other.q); + raSearch = std::move(other.raSearch); + + // Reset other model. + other.treeType = TreeTypes::KD_TREE; + other.leafSize = 20; + other.randomBasis = false; + } + + return *this; +} + +// Clean memory, if necessary +inline RAModel::~RAModel() +{ + delete raSearch; +} + +inline void RAModel::InitializeModel(const bool naive, + const bool singleMode) +{ + // Clean memory, if necessary. + delete raSearch; + + switch (treeType) + { + case KD_TREE: + raSearch = new LeafSizeRAWrapper(naive, singleMode); + break; + case COVER_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case R_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case R_STAR_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case X_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case HILBERT_R_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case R_PLUS_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case R_PLUS_PLUS_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case UB_TREE: + raSearch = new LeafSizeRAWrapper(naive, singleMode); + break; + case OCTREE: + raSearch = new LeafSizeRAWrapper(naive, singleMode); + break; + } +} + +inline void RAModel::BuildModel(util::Timers& timers, + arma::mat&& referenceSet, + const size_t leafSize, + const bool naive, + const bool singleMode) +{ + // Initialize random basis, if necessary. + if (randomBasis) + { + timers.Start("computing_random_basis"); + Log::Info << "Creating random basis..." << std::endl; + math::RandomBasis(q, referenceSet.n_rows); + + referenceSet = q * referenceSet; + timers.Stop("computing_random_basis"); + } + + this->leafSize = leafSize; + + if (!naive) + Log::Info << "Building reference tree..." << std::endl; + + InitializeModel(naive, singleMode); + + raSearch->Train(timers, std::move(referenceSet), leafSize); + + if (!naive) + Log::Info << "Tree built." << std::endl; +} + +inline void RAModel::Search(util::Timers& timers, + arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances) +{ + // Apply the random basis if necessary. + if (randomBasis) + querySet = q * querySet; + + Log::Info << "Searching for " << k << " approximate nearest neighbors with "; + if (!Naive() && !SingleMode()) + Log::Info << "dual-tree rank-approximate " << TreeName() << " search..."; + else if (!Naive()) + Log::Info << "single-tree rank-approximate " << TreeName() << " search..."; + else + Log::Info << "brute-force (naive) rank-approximate search..."; + Log::Info << std::endl; + + raSearch->Search(timers, std::move(querySet), k, neighbors, distances, + leafSize); +} + +inline void RAModel::Search(util::Timers& timers, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances) +{ + Log::Info << "Searching for " << k << " approximate nearest neighbors with "; + if (!Naive() && !SingleMode()) + Log::Info << "dual-tree rank-approximate " << TreeName() << " search..."; + else if (!Naive()) + Log::Info << "single-tree rank-approximate " << TreeName() << " search..."; + else + Log::Info << "brute-force (naive) rank-approximate search..."; + Log::Info << std::endl; + + raSearch->Search(timers, k, neighbors, distances); +} + +inline std::string RAModel::TreeName() const +{ + switch (treeType) + { + case KD_TREE: + return "kd-tree"; + case COVER_TREE: + return "cover tree"; + case R_TREE: + return "R tree"; + case R_STAR_TREE: + return "R* tree"; + case X_TREE: + return "X tree"; + case HILBERT_R_TREE: + return "Hilbert R tree"; + case R_PLUS_TREE: + return "R+ tree"; + case R_PLUS_PLUS_TREE: + return "R++ tree"; + case UB_TREE: + return "UB tree"; + case OCTREE: + return "octree"; + default: + return "unknown tree"; + } +} + template class TreeType> diff --git a/src/mlpack/methods/rann/ra_util.hpp b/src/mlpack/methods/rann/ra_util.hpp index a4ccc87d36..a2ef07f763 100644 --- a/src/mlpack/methods/rann/ra_util.hpp +++ b/src/mlpack/methods/rann/ra_util.hpp @@ -48,22 +48,12 @@ class RAUtil const size_t k, const size_t m, const size_t t); - - /** - * Pick up desired number of samples (with replacement) from a given range - * of integers so that only the distinct samples are returned from - * the range [0 - specified upper bound) - * - * @param numSamples Number of random samples. - * @param rangeUpperBound The upper bound on the range of integers. - * @param distinctSamples The list of the distinct samples. - */ - static void ObtainDistinctSamples(const size_t numSamples, - const size_t rangeUpperBound, - arma::uvec& distinctSamples); }; } // namespace neighbor } // namespace mlpack +// Include implementation. +#include "ra_util_impl.hpp" + #endif diff --git a/src/mlpack/methods/rann/ra_util.cpp b/src/mlpack/methods/rann/ra_util_impl.hpp similarity index 83% rename from src/mlpack/methods/rann/ra_util.cpp rename to src/mlpack/methods/rann/ra_util_impl.hpp index 50aceed04e..1804aeb01a 100644 --- a/src/mlpack/methods/rann/ra_util.cpp +++ b/src/mlpack/methods/rann/ra_util_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/rann/ra_util.cpp + * @file methods/rann/ra_util_impl.hpp * @author Parikshit Ram * @author Ryan Curtin * @@ -10,15 +10,18 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_METHODS_RANN_RA_UTIL_IMPL_HPP +#define MLPACK_METHODS_RANN_RA_UTIL_IMPL_HPP + #include "ra_util.hpp" -using namespace mlpack; -using namespace mlpack::neighbor; +namespace mlpack { +namespace neighbor { -size_t mlpack::neighbor::RAUtil::MinimumSamplesReqd(const size_t n, - const size_t k, - const double tau, - const double alpha) +inline size_t RAUtil::MinimumSamplesReqd(const size_t n, + const size_t k, + const double tau, + const double alpha) { size_t ub = n; // The upper bound on the binary search. size_t lb = k; // The lower bound on the binary search. @@ -69,10 +72,10 @@ size_t mlpack::neighbor::RAUtil::MinimumSamplesReqd(const size_t n, return (std::min(m + 1, n)); } -double mlpack::neighbor::RAUtil::SuccessProbability(const size_t n, - const size_t k, - const size_t m, - const size_t t) +inline double RAUtil::SuccessProbability(const size_t n, + const size_t k, + const size_t m, + const size_t t) { if (k == 1) { @@ -161,3 +164,8 @@ double mlpack::neighbor::RAUtil::SuccessProbability(const size_t n, return sum; } // For k > 1. } + +} // namespace neighbor +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/regularized_svd/regularized_svd_function_impl.hpp b/src/mlpack/methods/regularized_svd/regularized_svd_function_impl.hpp index 6dc2510e27..1a3ec638be 100644 --- a/src/mlpack/methods/regularized_svd/regularized_svd_function_impl.hpp +++ b/src/mlpack/methods/regularized_svd/regularized_svd_function_impl.hpp @@ -272,7 +272,7 @@ inline double ParallelSGD::Optimize( if (shuffle) // Determine order of visitation. std::shuffle(visitationOrder.begin(), visitationOrder.end(), - mlpack::math::randGen); + mlpack::math::RandGen()); #pragma omp parallel { diff --git a/src/mlpack/methods/reinforcement_learning/environment/CMakeLists.txt b/src/mlpack/methods/reinforcement_learning/environment/CMakeLists.txt index be5d1f3486..77d822aced 100644 --- a/src/mlpack/methods/reinforcement_learning/environment/CMakeLists.txt +++ b/src/mlpack/methods/reinforcement_learning/environment/CMakeLists.txt @@ -2,7 +2,6 @@ # Anything not in this list will not be compiled into mlpack. set(SOURCES env_type.hpp - env_type.cpp mountain_car.hpp cart_pole.hpp continuous_mountain_car.hpp diff --git a/src/mlpack/methods/reinforcement_learning/environment/env_type.cpp b/src/mlpack/methods/reinforcement_learning/environment/env_type.cpp deleted file mode 100644 index 98f0799199..0000000000 --- a/src/mlpack/methods/reinforcement_learning/environment/env_type.cpp +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @file methods/reinforcement_learning/environment/env_type.cpp - * @author Nishant Kumar - * - * This file defines the static variables used by the discrete and continuous - * environments. - * - * 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. - */ -#include "env_type.hpp" - -namespace mlpack { -namespace rl { - -// Instantiate static members. - -size_t DiscreteActionEnv::State::dimension = 0; -size_t DiscreteActionEnv::Action::size = 0; -size_t DiscreteActionEnv::rewardSize = 0; - -size_t ContinuousActionEnv::State::dimension = 0; -size_t ContinuousActionEnv::Action::size = 0; -size_t ContinuousActionEnv::rewardSize = 0; - -} // namespace rl -} // namespace mlpack diff --git a/src/mlpack/methods/reinforcement_learning/environment/env_type.hpp b/src/mlpack/methods/reinforcement_learning/environment/env_type.hpp index 46a4c746bf..98bd8963b9 100644 --- a/src/mlpack/methods/reinforcement_learning/environment/env_type.hpp +++ b/src/mlpack/methods/reinforcement_learning/environment/env_type.hpp @@ -206,6 +206,19 @@ class ContinuousActionEnv static size_t rewardSize; }; +#ifndef MLPACK_METHODS_RL_ENVIRONMENT_ENV_TYPE_VARIABLES +#define MLPACK_METHODS_RL_ENVIRONMENT_ENV_TYPE_VARIABLES + +size_t DiscreteActionEnv::State::dimension = 0; +size_t DiscreteActionEnv::Action::size = 0; +size_t DiscreteActionEnv::rewardSize = 0; + +size_t ContinuousActionEnv::State::dimension = 0; +size_t ContinuousActionEnv::Action::size = 0; +size_t ContinuousActionEnv::rewardSize = 0; + +#endif + } // namespace rl } // namespace mlpack diff --git a/src/mlpack/methods/softmax_regression/CMakeLists.txt b/src/mlpack/methods/softmax_regression/CMakeLists.txt index e0aa0d14f9..fa8ea108b5 100644 --- a/src/mlpack/methods/softmax_regression/CMakeLists.txt +++ b/src/mlpack/methods/softmax_regression/CMakeLists.txt @@ -2,10 +2,9 @@ # Anything not in this list will not be compiled into mlpack. set(SOURCES softmax_regression.hpp - softmax_regression.cpp softmax_regression_impl.hpp softmax_regression_function.hpp - softmax_regression_function.cpp + softmax_regression_function_impl.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/softmax_regression/softmax_regression.cpp b/src/mlpack/methods/softmax_regression/softmax_regression.cpp deleted file mode 100644 index 567241b35a..0000000000 --- a/src/mlpack/methods/softmax_regression/softmax_regression.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/** - * @file methods/softmax_regression/softmax_regression.cpp - * @author Siddharth Agrawal - * - * Implementation of softmax regression. - * - * 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. - */ - -#include "softmax_regression.hpp" - -namespace mlpack { -namespace regression { - -SoftmaxRegression:: -SoftmaxRegression(const size_t inputSize, - const size_t numClasses, - const bool fitIntercept) : - numClasses(numClasses), - lambda(0.0001), - fitIntercept(fitIntercept) -{ - SoftmaxRegressionFunction::InitializeWeights( - parameters, inputSize, numClasses, fitIntercept); -} - -void SoftmaxRegression::Classify(const arma::mat& dataset, - arma::Row& labels) - const -{ - arma::mat probabilities; - Classify(dataset, probabilities); - - // Prepare necessary data. - labels.zeros(dataset.n_cols); - double maxProbability = 0; - - // For each test input. - for (size_t i = 0; i < dataset.n_cols; ++i) - { - // For each class. - for (size_t j = 0; j < numClasses; ++j) - { - // If a higher class probability is encountered, change prediction. - if (probabilities(j, i) > maxProbability) - { - maxProbability = probabilities(j, i); - labels(i) = j; - } - } - - // Set maximum probability to zero for the next input. - maxProbability = 0; - } -} - -void SoftmaxRegression::Classify(const arma::mat& dataset, - arma::Row& labels, - arma::mat& probabilities) - const -{ - Classify(dataset, probabilities); - - // Prepare necessary data. - labels.zeros(dataset.n_cols); - double maxProbability = 0; - - // For each test input. - for (size_t i = 0; i < dataset.n_cols; ++i) - { - // For each class. - for (size_t j = 0; j < numClasses; ++j) - { - // If a higher class probability is encountered, change prediction. - if (probabilities(j, i) > maxProbability) - { - maxProbability = probabilities(j, i); - labels(i) = j; - } - } - - // Set maximum probability to zero for the next input. - maxProbability = 0; - } -} - -void SoftmaxRegression::Classify(const arma::mat& dataset, - arma::mat& probabilities) - const -{ - util::CheckSameDimensionality(dataset, FeatureSize(), - "SoftmaxRegression::Classify()"); - - // Calculate the probabilities for each test input. - arma::mat hypothesis; - if (fitIntercept) - { - // In order to add the intercept term, we should compute following matrix: - // [1; data] = arma::join_cols(ones(1, data.n_cols), data) - // hypothesis = arma::exp(parameters * [1; data]). - // - // Since the cost of join maybe high due to the copy of original data, - // split the hypothesis computation to two components. - hypothesis = arma::exp( - arma::repmat(parameters.col(0), 1, dataset.n_cols) + - parameters.cols(1, parameters.n_cols - 1) * dataset); - } - else - { - hypothesis = arma::exp(parameters * dataset); - } - - probabilities = hypothesis / arma::repmat(arma::sum(hypothesis, 0), - numClasses, 1); -} - -double SoftmaxRegression::ComputeAccuracy( - const arma::mat& testData, - const arma::Row& labels) const -{ - arma::Row predictions; - - // Get predictions for the provided data. - Classify(testData, predictions); - - // Increment count for every correctly predicted label. - size_t count = 0; - for (size_t i = 0; i < predictions.n_elem; ++i) - if (predictions(i) == labels(i)) - count++; - - // Return percentage accuracy. - return (count * 100.0) / predictions.n_elem; -} - -} // namespace regression -} // namespace mlpack diff --git a/src/mlpack/methods/softmax_regression/softmax_regression_function.hpp b/src/mlpack/methods/softmax_regression/softmax_regression_function.hpp index 952d81b6a9..181e035bed 100644 --- a/src/mlpack/methods/softmax_regression/softmax_regression_function.hpp +++ b/src/mlpack/methods/softmax_regression/softmax_regression_function.hpp @@ -14,6 +14,7 @@ #define MLPACK_METHODS_SOFTMAX_REGRESSION_SOFTMAX_REGRESSION_FUNCTION_HPP #include +#include namespace mlpack { namespace regression { @@ -205,4 +206,7 @@ class SoftmaxRegressionFunction } // namespace regression } // namespace mlpack +// Include implementation. +#include "softmax_regression_function_impl.hpp" + #endif diff --git a/src/mlpack/methods/softmax_regression/softmax_regression_function.cpp b/src/mlpack/methods/softmax_regression/softmax_regression_function_impl.hpp similarity index 84% rename from src/mlpack/methods/softmax_regression/softmax_regression_function.cpp rename to src/mlpack/methods/softmax_regression/softmax_regression_function_impl.hpp index 2e8a93b79c..e91205552e 100644 --- a/src/mlpack/methods/softmax_regression/softmax_regression_function.cpp +++ b/src/mlpack/methods/softmax_regression/softmax_regression_function_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/softmax_regression/softmax_regression_function.cpp + * @file methods/softmax_regression/softmax_regression_function_impl.hpp * @author Siddharth Agrawal * * Implementation of function to be optimized for softmax regression. @@ -9,13 +9,15 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_METHODS_SOFTMAX_REGRESSION_SOFTMAX_REGRESSION_FUNCTION_IMPL_HPP +#define MLPACK_METHODS_SOFTMAX_REGRESSION_SOFTMAX_REGRESSION_FUNCTION_IMPL_HPP + #include "softmax_regression_function.hpp" -#include -using namespace mlpack; -using namespace mlpack::regression; +namespace mlpack { +namespace regression { -SoftmaxRegressionFunction::SoftmaxRegressionFunction( +inline SoftmaxRegressionFunction::SoftmaxRegressionFunction( const arma::mat& data, const arma::Row& labels, const size_t numClasses, @@ -36,7 +38,7 @@ SoftmaxRegressionFunction::SoftmaxRegressionFunction( /** * Shuffle the data. */ -void SoftmaxRegressionFunction::Shuffle() +inline void SoftmaxRegressionFunction::Shuffle() { // Determine new ordering. arma::uvec ordering = arma::shuffle(arma::linspace(0, @@ -75,12 +77,12 @@ void SoftmaxRegressionFunction::Shuffle() * normal distribution. The weights cannot be initialized to zero, as that will * lead to each class output being the same. */ -const arma::mat SoftmaxRegressionFunction::InitializeWeights() +inline const arma::mat SoftmaxRegressionFunction::InitializeWeights() { return InitializeWeights(data.n_rows, numClasses, fitIntercept); } -const arma::mat SoftmaxRegressionFunction::InitializeWeights( +inline const arma::mat SoftmaxRegressionFunction::InitializeWeights( const size_t featureSize, const size_t numClasses, const bool fitIntercept) @@ -90,7 +92,7 @@ const arma::mat SoftmaxRegressionFunction::InitializeWeights( return parameters; } -void SoftmaxRegressionFunction::InitializeWeights( +inline void SoftmaxRegressionFunction::InitializeWeights( arma::mat &weights, const size_t featureSize, const size_t numClasses, @@ -111,7 +113,7 @@ void SoftmaxRegressionFunction::InitializeWeights( * labels. The output is in the form of a matrix, which leads to simpler * calculations in the Evaluate() and Gradient() methods. */ -void SoftmaxRegressionFunction::GetGroundTruthMatrix( +inline void SoftmaxRegressionFunction::GetGroundTruthMatrix( const arma::Row& labels, arma::sp_mat& groundTruth) { // Calculate the ground truth matrix according to the labels passed. The @@ -147,7 +149,7 @@ void SoftmaxRegressionFunction::GetGroundTruthMatrix( * Evaluate the probabilities matrix. If fitIntercept flag is true, * it should consider the parameters.cols(0) intercept term. */ -void SoftmaxRegressionFunction::GetProbabilitiesMatrix( +inline void SoftmaxRegressionFunction::GetProbabilitiesMatrix( const arma::mat& parameters, arma::mat& probabilities, const size_t start, @@ -181,7 +183,8 @@ void SoftmaxRegressionFunction::GetProbabilitiesMatrix( /** * Evaluates the objective function given the parameters. */ -double SoftmaxRegressionFunction::Evaluate(const arma::mat& parameters) const +inline double SoftmaxRegressionFunction::Evaluate(const arma::mat& parameters) + const { // The objective function is the negative log likelihood of the model // calculated over all the training examples. Mathematically it is as follows: @@ -219,9 +222,9 @@ double SoftmaxRegressionFunction::Evaluate(const arma::mat& parameters) const /** * Evaluate the objective function for the given points given the parameters. */ -double SoftmaxRegressionFunction::Evaluate(const arma::mat& parameters, - const size_t start, - const size_t batchSize) const +inline double SoftmaxRegressionFunction::Evaluate(const arma::mat& parameters, + const size_t start, + const size_t batchSize) const { arma::mat probabilities; GetProbabilitiesMatrix(parameters, probabilities, start, batchSize); @@ -239,8 +242,8 @@ double SoftmaxRegressionFunction::Evaluate(const arma::mat& parameters, /** * Calculates and stores the gradient values given a set of parameters. */ -void SoftmaxRegressionFunction::Gradient(const arma::mat& parameters, - arma::mat& gradient) const +inline void SoftmaxRegressionFunction::Gradient(const arma::mat& parameters, + arma::mat& gradient) const { // Calculate the class probabilities for each training example. The // probabilities for each of the classes are given by: @@ -272,10 +275,11 @@ void SoftmaxRegressionFunction::Gradient(const arma::mat& parameters, } } -void SoftmaxRegressionFunction::Gradient(const arma::mat& parameters, - const size_t start, - arma::mat& gradient, - const size_t batchSize) const +inline void SoftmaxRegressionFunction::Gradient(const arma::mat& parameters, + const size_t start, + arma::mat& gradient, + const size_t batchSize) + const { arma::mat probabilities; GetProbabilitiesMatrix(parameters, probabilities, start, batchSize); @@ -301,9 +305,10 @@ void SoftmaxRegressionFunction::Gradient(const arma::mat& parameters, } } -void SoftmaxRegressionFunction::PartialGradient(const arma::mat& parameters, - const size_t j, - arma::sp_mat& gradient) const +inline void SoftmaxRegressionFunction::PartialGradient(const arma::mat& parameters, + const size_t j, + arma::sp_mat& gradient) + const { gradient.zeros(arma::size(parameters)); @@ -332,3 +337,8 @@ void SoftmaxRegressionFunction::PartialGradient(const arma::mat& parameters, parameters.col(j); } } + +} // namespace regression +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/softmax_regression/softmax_regression_impl.hpp b/src/mlpack/methods/softmax_regression/softmax_regression_impl.hpp index 69b2a35621..c46abcedaf 100644 --- a/src/mlpack/methods/softmax_regression/softmax_regression_impl.hpp +++ b/src/mlpack/methods/softmax_regression/softmax_regression_impl.hpp @@ -49,6 +49,108 @@ SoftmaxRegression::SoftmaxRegression( Train(data, labels, numClasses, optimizer, callbacks...); } +inline SoftmaxRegression::SoftmaxRegression( + const size_t inputSize, + const size_t numClasses, + const bool fitIntercept) : + numClasses(numClasses), + lambda(0.0001), + fitIntercept(fitIntercept) +{ + SoftmaxRegressionFunction::InitializeWeights( + parameters, inputSize, numClasses, fitIntercept); +} + +inline void SoftmaxRegression::Classify(const arma::mat& dataset, + arma::Row& labels) + const +{ + arma::mat probabilities; + Classify(dataset, probabilities); + + // Prepare necessary data. + labels.zeros(dataset.n_cols); + double maxProbability = 0; + + // For each test input. + for (size_t i = 0; i < dataset.n_cols; ++i) + { + // For each class. + for (size_t j = 0; j < numClasses; ++j) + { + // If a higher class probability is encountered, change prediction. + if (probabilities(j, i) > maxProbability) + { + maxProbability = probabilities(j, i); + labels(i) = j; + } + } + + // Set maximum probability to zero for the next input. + maxProbability = 0; + } +} + +inline void SoftmaxRegression::Classify(const arma::mat& dataset, + arma::Row& labels, + arma::mat& probabilities) + const +{ + Classify(dataset, probabilities); + + // Prepare necessary data. + labels.zeros(dataset.n_cols); + double maxProbability = 0; + + // For each test input. + for (size_t i = 0; i < dataset.n_cols; ++i) + { + // For each class. + for (size_t j = 0; j < numClasses; ++j) + { + // If a higher class probability is encountered, change prediction. + if (probabilities(j, i) > maxProbability) + { + maxProbability = probabilities(j, i); + labels(i) = j; + } + } + + // Set maximum probability to zero for the next input. + maxProbability = 0; + } +} + +inline void SoftmaxRegression::Classify(const arma::mat& dataset, + arma::mat& probabilities) + const +{ + util::CheckSameDimensionality(dataset, FeatureSize(), + "SoftmaxRegression::Classify()"); + + // Calculate the probabilities for each test input. + arma::mat hypothesis; + if (fitIntercept) + { + // In order to add the intercept term, we should compute following matrix: + // [1; data] = arma::join_cols(ones(1, data.n_cols), data) + // hypothesis = arma::exp(parameters * [1; data]). + // + // Since the cost of join maybe high due to the copy of original data, + // split the hypothesis computation to two components. + hypothesis = arma::exp( + arma::repmat(parameters.col(0), 1, dataset.n_cols) + + parameters.cols(1, parameters.n_cols - 1) * dataset); + } + else + { + hypothesis = arma::exp(parameters * dataset); + } + + probabilities = hypothesis / arma::repmat(arma::sum(hypothesis, 0), + numClasses, 1); +} + template size_t SoftmaxRegression::Classify(const VecType& point) const { @@ -57,6 +159,25 @@ size_t SoftmaxRegression::Classify(const VecType& point) const return size_t(label(0)); } +inline double SoftmaxRegression::ComputeAccuracy( + const arma::mat& testData, + const arma::Row& labels) const +{ + arma::Row predictions; + + // Get predictions for the provided data. + Classify(testData, predictions); + + // Increment count for every correctly predicted label. + size_t count = 0; + for (size_t i = 0; i < predictions.n_elem; ++i) + if (predictions(i) == labels(i)) + count++; + + // Return percentage accuracy. + return (count * 100.0) / predictions.n_elem; +} + template double SoftmaxRegression::Train(const arma::mat& data, const arma::Row& labels, diff --git a/src/mlpack/methods/sparse_autoencoder/CMakeLists.txt b/src/mlpack/methods/sparse_autoencoder/CMakeLists.txt index 799d9cafcd..db505bb46d 100644 --- a/src/mlpack/methods/sparse_autoencoder/CMakeLists.txt +++ b/src/mlpack/methods/sparse_autoencoder/CMakeLists.txt @@ -2,12 +2,11 @@ # Anything not in this list will not be compiled into mlpack. set(SOURCES sparse_autoencoder.hpp - sparse_autoencoder.cpp sparse_autoencoder_impl.hpp sparse_autoencoder_function.hpp - sparse_autoencoder_function.cpp + sparse_autoencoder_function_impl.hpp maximal_inputs.hpp - maximal_inputs.cpp + maximal_inputs_impl.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/sparse_autoencoder/maximal_inputs.hpp b/src/mlpack/methods/sparse_autoencoder/maximal_inputs.hpp index 887f5f0e15..2b9a6eec10 100644 --- a/src/mlpack/methods/sparse_autoencoder/maximal_inputs.hpp +++ b/src/mlpack/methods/sparse_autoencoder/maximal_inputs.hpp @@ -93,4 +93,7 @@ void NormalizeColByMax(const arma::mat& input, arma::mat& output); } // namespace nn } // namespace mlpack +// Include implementation. +#include "maximal_inputs_impl.hpp" + #endif diff --git a/src/mlpack/methods/sparse_autoencoder/maximal_inputs.cpp b/src/mlpack/methods/sparse_autoencoder/maximal_inputs_impl.hpp similarity index 75% rename from src/mlpack/methods/sparse_autoencoder/maximal_inputs.cpp rename to src/mlpack/methods/sparse_autoencoder/maximal_inputs_impl.hpp index 742cfecae2..d6ed1c497c 100644 --- a/src/mlpack/methods/sparse_autoencoder/maximal_inputs.cpp +++ b/src/mlpack/methods/sparse_autoencoder/maximal_inputs_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/sparse_autoencoder/maximal_inputs.cpp + * @file methods/sparse_autoencoder/maximal_inputs_impl.hpp * @author Tham Ngap Wei * * Implementation of MaximalInputs(). @@ -9,12 +9,15 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_METHODS_NN_MAXIMAL_INPUTS_IMPL_HPP +#define MLPACK_METHODS_NN_MAXIMAL_INPUTS_IMPL_HPP + #include "maximal_inputs.hpp" namespace mlpack { namespace nn { -void MaximalInputs(const arma::mat& parameters, arma::mat& output) +inline void MaximalInputs(const arma::mat& parameters, arma::mat& output) { arma::mat paramTemp(parameters.submat(0, 0, (parameters.n_rows - 1) / 2 - 1, parameters.n_cols - 2).t()); @@ -24,8 +27,8 @@ void MaximalInputs(const arma::mat& parameters, arma::mat& output) NormalizeColByMax(paramTemp, output); } -void NormalizeColByMax(const arma::mat &input, - arma::mat &output) +inline void NormalizeColByMax(const arma::mat &input, + arma::mat &output) { output.set_size(input.n_rows, input.n_cols); for (arma::uword i = 0; i != input.n_cols; ++i) @@ -44,3 +47,5 @@ void NormalizeColByMax(const arma::mat &input, } // namespace nn } // namespace mlpack + +#endif diff --git a/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder.cpp b/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder.cpp deleted file mode 100644 index 734b45998a..0000000000 --- a/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @file methods/sparse_autoencoder/sparse_autoencoder.cpp - * @author Siddharth Agrawal - * - * Implementation of sparse autoencoders. - * - * 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. - */ - -#include "sparse_autoencoder.hpp" - -namespace mlpack { -namespace nn { - -void SparseAutoencoder::GetNewFeatures(arma::mat& data, - arma::mat& features) -{ - const size_t l1 = hiddenSize; - const size_t l2 = visibleSize; - - Sigmoid(parameters.submat(0, 0, l1 - 1, l2 - 1) * data + - arma::repmat(parameters.submat(0, l2, l1 - 1, l2), 1, data.n_cols), - features); -} - -} // namespace nn -} // namespace mlpack diff --git a/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_function.hpp b/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_function.hpp index dcfea042d7..904343e09e 100644 --- a/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_function.hpp +++ b/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_function.hpp @@ -165,4 +165,7 @@ class SparseAutoencoderFunction } // namespace nn } // namespace mlpack +// Include implementation. +#include "sparse_autoencoder_function_impl.hpp" + #endif diff --git a/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_function.cpp b/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_function_impl.hpp similarity index 90% rename from src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_function.cpp rename to src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_function_impl.hpp index 4916c021fd..5829f99b40 100644 --- a/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_function.cpp +++ b/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_function_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/sparse_autoencoder/sparse_autoencoder_function.cpp + * @file methods/sparse_autoencoder/sparse_autoencoder_function_impl.hpp * @author Siddharth Agrawal * * Implementation of function to be optimized for sparse autoencoders. @@ -9,18 +9,21 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ +#ifndef MLPACK_METHODS_SPARSE_AUTOENCODER_SPARSE_AUTOENCODER_FUNCTION_IMPL_HPP +#define MLPACK_METHODS_SPARSE_AUTOENCODER_SPARSE_AUTOENCODER_FUNCTION_IMPL_HPP + #include "sparse_autoencoder_function.hpp" -using namespace mlpack; -using namespace mlpack::nn; -using namespace std; +namespace mlpack { +namespace nn { -SparseAutoencoderFunction::SparseAutoencoderFunction(const arma::mat& data, - const size_t visibleSize, - const size_t hiddenSize, - const double lambda, - const double beta, - const double rho) : +inline SparseAutoencoderFunction::SparseAutoencoderFunction( + const arma::mat& data, + const size_t visibleSize, + const size_t hiddenSize, + const double lambda, + const double beta, + const double rho) : data(data), visibleSize(visibleSize), hiddenSize(hiddenSize), @@ -37,7 +40,7 @@ SparseAutoencoderFunction::SparseAutoencoderFunction(const arma::mat& data, * [-r, r] where 'r' is decided using the sizes of the visible and hidden * layers. The biases b1, b2 are initialized to 0. */ -const arma::mat SparseAutoencoderFunction::InitializeWeights() +inline const arma::mat SparseAutoencoderFunction::InitializeWeights() { // The module uses a matrix to store the parameters, its structure looks like: // vSize 1 @@ -73,7 +76,8 @@ const arma::mat SparseAutoencoderFunction::InitializeWeights() /** Evaluates the objective function given the parameters. */ -double SparseAutoencoderFunction::Evaluate(const arma::mat& parameters) const +inline double SparseAutoencoderFunction::Evaluate(const arma::mat& parameters) + const { // The objective function is the average squared reconstruction error of the // network. w1 and b1 are the weights and biases associated with the hidden @@ -141,8 +145,8 @@ double SparseAutoencoderFunction::Evaluate(const arma::mat& parameters) const /** Calculates and stores the gradient values given a set of parameters. */ -void SparseAutoencoderFunction::Gradient(const arma::mat& parameters, - arma::mat& gradient) const +inline void SparseAutoencoderFunction::Gradient(const arma::mat& parameters, + arma::mat& gradient) const { // Performs a feedforward pass of the neural network, and computes the // activations of the output layer as in the Evaluate() method. It uses the @@ -208,3 +212,8 @@ void SparseAutoencoderFunction::Gradient(const arma::mat& parameters, gradient.submat(0, l2, l1 - 1, l2) = arma::sum(delHid, 1) / data.n_cols; gradient.submat(l3, 0, l3, l2 - 1) = (arma::sum(delOut, 1) / data.n_cols).t(); } + +} // namespace nn +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_impl.hpp b/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_impl.hpp index ecf788c86c..4fcd6d860d 100644 --- a/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_impl.hpp +++ b/src/mlpack/methods/sparse_autoencoder/sparse_autoencoder_impl.hpp @@ -72,6 +72,17 @@ SparseAutoencoder::SparseAutoencoder(const arma::mat& data, << "trained model is " << out << "." << std::endl; } +inline void SparseAutoencoder::GetNewFeatures(arma::mat& data, + arma::mat& features) +{ + const size_t l1 = hiddenSize; + const size_t l2 = visibleSize; + + Sigmoid(parameters.submat(0, 0, l1 - 1, l2 - 1) * data + + arma::repmat(parameters.submat(0, l2, l1 - 1, l2), 1, data.n_cols), + features); +} + } // namespace nn } // namespace mlpack diff --git a/src/mlpack/methods/sparse_coding/CMakeLists.txt b/src/mlpack/methods/sparse_coding/CMakeLists.txt index ed72066c8e..73345260d5 100644 --- a/src/mlpack/methods/sparse_coding/CMakeLists.txt +++ b/src/mlpack/methods/sparse_coding/CMakeLists.txt @@ -5,7 +5,6 @@ set(SOURCES nothing_initializer.hpp random_initializer.hpp sparse_coding.hpp - sparse_coding.cpp sparse_coding_impl.hpp ) diff --git a/src/mlpack/methods/sparse_coding/sparse_coding.cpp b/src/mlpack/methods/sparse_coding/sparse_coding.cpp deleted file mode 100644 index c0b69082fe..0000000000 --- a/src/mlpack/methods/sparse_coding/sparse_coding.cpp +++ /dev/null @@ -1,277 +0,0 @@ -/** - * @file methods/sparse_coding/sparse_coding.cpp - * @author Nishant Mehta - * - * Implementation of Sparse Coding with Dictionary Learning using l1 (LASSO) or - * l1+l2 (Elastic Net) regularization. - * - * 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. - */ -#include "sparse_coding.hpp" -#include - -namespace mlpack { -namespace sparse_coding { - -SparseCoding::SparseCoding( - const size_t atoms, - const double lambda1, - const double lambda2, - const size_t maxIterations, - const double objTolerance, - const double newtonTolerance) : - atoms(atoms), - lambda1(lambda1), - lambda2(lambda2), - maxIterations(maxIterations), - objTolerance(objTolerance), - newtonTolerance(newtonTolerance) -{ - // Nothing to do. -} - -void SparseCoding::Encode(const arma::mat& data, arma::mat& codes) -{ - // When using the Cholesky version of LARS, this is correct even if - // lambda2 > 0. - arma::mat matGram = trans(dictionary) * dictionary; - - codes.set_size(atoms, data.n_cols); - for (size_t i = 0; i < data.n_cols; ++i) - { - // Report progress. - if ((i % 100) == 0) - Log::Debug << "Optimization at point " << i << "." << std::endl; - - bool useCholesky = true; - regression::LARS lars(useCholesky, matGram, lambda1, lambda2); - - // Create an alias of the code (using the same memory), and then LARS will - // place the result directly into that; then we will not need to have an - // extra copy. - arma::vec code = codes.unsafe_col(i); - arma::rowvec responses = data.unsafe_col(i).t(); - lars.Train(dictionary, responses, code, false); - } -} - -// Dictionary step for optimization. -double SparseCoding::OptimizeDictionary(const arma::mat& data, - const arma::mat& codes, - const arma::uvec& adjacencies) -{ - // Count the number of atomic neighbors for each point x^i. - arma::uvec neighborCounts = arma::zeros(data.n_cols, 1); - - if (adjacencies.n_elem > 0) - { - // This gets the column index. Intentional integer division. - size_t curPointInd = (size_t) (adjacencies(0) / atoms); - - size_t nextColIndex = (curPointInd + 1) * atoms; - for (size_t l = 1; l < adjacencies.n_elem; ++l) - { - // If l no longer refers to an element in this column, advance the column - // number accordingly. - if (adjacencies(l) >= nextColIndex) - { - curPointInd = (size_t) (adjacencies(l) / atoms); - nextColIndex = (curPointInd + 1) * atoms; - } - - ++neighborCounts(curPointInd); - } - } - - // Handle the case of inactive atoms (atoms not used in the given coding). - std::vector inactiveAtoms; - - for (size_t j = 0; j < atoms; ++j) - { - if (arma::accu(codes.row(j) != 0) == 0) - inactiveAtoms.push_back(j); - } - - const size_t nInactiveAtoms = inactiveAtoms.size(); - const size_t nActiveAtoms = atoms - nInactiveAtoms; - - // Efficient construction of Z restricted to active atoms. - arma::mat matActiveZ; - if (nInactiveAtoms > 0) - { - math::RemoveRows(codes, inactiveAtoms, matActiveZ); - } - - if (nInactiveAtoms > 0) - { - Log::Warn << "There are " << nInactiveAtoms - << " inactive atoms. They will be re-initialized randomly.\n"; - } - - Log::Debug << "Solving Dual via Newton's Method.\n"; - - // Solve using Newton's method in the dual - note that the final dot - // multiplication with inv(A) seems to be unavoidable. Although more - // expensive, the code written this way (we use solve()) should be more - // numerically stable than just using inv(A) for everything. - arma::vec dualVars = arma::zeros(nActiveAtoms); - - // vec dualVars = 1e-14 * ones(nActiveAtoms); - - // Method used by feature sign code - fails miserably here. Perhaps the - // MATLAB optimizer fmincon does something clever? - // vec dualVars = 10.0 * randu(nActiveAtoms, 1); - - // vec dualVars = diagvec(solve(dictionary, data * trans(codes)) - // - codes * trans(codes)); - // for (size_t i = 0; i < dualVars.n_elem; ++i) - // if (dualVars(i) < 0) - // dualVars(i) = 0; - - bool converged = false; - - // If we have any inactive atoms, we must construct these differently. - arma::mat codesXT; - arma::mat codesZT; - - if (inactiveAtoms.empty()) - { - codesXT = codes * trans(data); - codesZT = codes * trans(codes); - } - else - { - codesXT = matActiveZ * trans(data); - codesZT = matActiveZ * trans(matActiveZ); - } - - double normGradient = 0; - double improvement = 0; - for (size_t t = 1; (t != maxIterations) && !converged; ++t) - { - arma::mat A = codesZT + diagmat(dualVars); - - arma::mat matAInvZXT = solve(A, codesXT); - - arma::vec gradient = -arma::sum(arma::square(matAInvZXT), 1); - gradient += 1; - - arma::mat hessian = -(-2 * (matAInvZXT * trans(matAInvZXT)) % inv(A)); - - arma::vec searchDirection = -solve(hessian, gradient); - - // Armijo line search. - const double c = 1e-4; - double alpha = 1.0; - const double rho = 0.9; - double sufficientDecrease = c * dot(gradient, searchDirection); - - // A maxIterations parameter for the Armijo line search may be a good idea, - // but it doesn't seem to be causing any problems for now. - while (true) - { - // Calculate objective. - double sumDualVars = arma::sum(dualVars); - double fOld = -(-trace(trans(codesXT) * matAInvZXT) - sumDualVars); - double fNew = -(-trace(trans(codesXT) * solve(codesZT + - diagmat(dualVars + alpha * searchDirection), codesXT)) - - (sumDualVars + alpha * arma::sum(searchDirection))); - - if (fNew <= fOld + alpha * sufficientDecrease) - { - searchDirection = alpha * searchDirection; - improvement = fOld - fNew; - break; - } - - alpha *= rho; - } - - // Take step and print useful information. - dualVars += searchDirection; - normGradient = arma::norm(gradient, 2); - Log::Debug << "Newton Method iteration " << t << ":" << std::endl; - Log::Debug << " Gradient norm: " << std::scientific << normGradient - << "." << std::endl; - Log::Debug << " Improvement: " << std::scientific << improvement << ".\n"; - - if (normGradient < newtonTolerance) - converged = true; - } - - if (inactiveAtoms.empty()) - { - // Directly update dictionary. - dictionary = trans(solve(codesZT + diagmat(dualVars), codesXT)); - } - else - { - arma::mat activeDictionary = trans(solve(codesZT + - diagmat(dualVars), codesXT)); - - // Update all atoms. - size_t currentInactiveIndex = 0; - for (size_t i = 0; i < atoms; ++i) - { - if (inactiveAtoms[currentInactiveIndex] == i) - { - // This atom is inactive. Reinitialize it randomly. - dictionary.col(i) = (data.col(math::RandInt(data.n_cols)) + - data.col(math::RandInt(data.n_cols)) + - data.col(math::RandInt(data.n_cols))); - - dictionary.col(i) /= arma::norm(dictionary.col(i), 2); - - // Increment inactive index counter. - ++currentInactiveIndex; - } - else - { - // Update estimate. - dictionary.col(i) = activeDictionary.col(i - currentInactiveIndex); - } - } - } - - return normGradient; -} - -// Project each atom of the dictionary back into the unit ball (if necessary). -void SparseCoding::ProjectDictionary() -{ - for (size_t j = 0; j < atoms; ++j) - { - double atomNorm = arma::norm(dictionary.col(j), 2); - if (atomNorm > 1) - { - Log::Info << "Norm of atom " << j << " exceeds 1 (" << std::scientific - << atomNorm << "). Shrinking...\n"; - dictionary.col(j) /= atomNorm; - } - } -} - -// Compute the objective function. -double SparseCoding::Objective(const arma::mat& data, const arma::mat& codes) - const -{ - double l11NormZ = arma::sum(arma::sum(arma::abs(codes))); - double froNormResidual = arma::norm(data - (dictionary * codes), "fro"); - - if (lambda2 > 0) - { - double froNormZ = arma::norm(codes, "fro"); - return 0.5 * (std::pow(froNormResidual, 2.0) + (lambda2 * - std::pow(froNormZ, 2.0))) + (lambda1 * l11NormZ); - } - else // It can be simpler. - { - return 0.5 * std::pow(froNormResidual, 2.0) + lambda1 * l11NormZ; - } -} - -} // namespace sparse_coding -} // namespace mlpack diff --git a/src/mlpack/methods/sparse_coding/sparse_coding.hpp b/src/mlpack/methods/sparse_coding/sparse_coding.hpp index 308d91a3f9..7ea6604568 100644 --- a/src/mlpack/methods/sparse_coding/sparse_coding.hpp +++ b/src/mlpack/methods/sparse_coding/sparse_coding.hpp @@ -15,6 +15,7 @@ #include #include +#include // Include our three simple dictionary initializers. #include "nothing_initializer.hpp" diff --git a/src/mlpack/methods/sparse_coding/sparse_coding_impl.hpp b/src/mlpack/methods/sparse_coding/sparse_coding_impl.hpp index 13ea3ebc19..7f9e29b10f 100644 --- a/src/mlpack/methods/sparse_coding/sparse_coding_impl.hpp +++ b/src/mlpack/methods/sparse_coding/sparse_coding_impl.hpp @@ -39,6 +39,265 @@ SparseCoding::SparseCoding( Train(data, initializer); } +inline SparseCoding::SparseCoding( + const size_t atoms, + const double lambda1, + const double lambda2, + const size_t maxIterations, + const double objTolerance, + const double newtonTolerance) : + atoms(atoms), + lambda1(lambda1), + lambda2(lambda2), + maxIterations(maxIterations), + objTolerance(objTolerance), + newtonTolerance(newtonTolerance) +{ + // Nothing to do. +} + +inline void SparseCoding::Encode(const arma::mat& data, + arma::mat& codes) +{ + // When using the Cholesky version of LARS, this is correct even if + // lambda2 > 0. + arma::mat matGram = trans(dictionary) * dictionary; + + codes.set_size(atoms, data.n_cols); + for (size_t i = 0; i < data.n_cols; ++i) + { + // Report progress. + if ((i % 100) == 0) + Log::Debug << "Optimization at point " << i << "." << std::endl; + + bool useCholesky = true; + regression::LARS lars(useCholesky, matGram, lambda1, lambda2); + + // Create an alias of the code (using the same memory), and then LARS will + // place the result directly into that; then we will not need to have an + // extra copy. + arma::vec code = codes.unsafe_col(i); + arma::rowvec responses = data.unsafe_col(i).t(); + lars.Train(dictionary, responses, code, false); + } +} + +// Dictionary step for optimization. +inline double SparseCoding::OptimizeDictionary(const arma::mat& data, + const arma::mat& codes, + const arma::uvec& adjacencies) +{ + // Count the number of atomic neighbors for each point x^i. + arma::uvec neighborCounts = arma::zeros(data.n_cols, 1); + + if (adjacencies.n_elem > 0) + { + // This gets the column index. Intentional integer division. + size_t curPointInd = (size_t) (adjacencies(0) / atoms); + + size_t nextColIndex = (curPointInd + 1) * atoms; + for (size_t l = 1; l < adjacencies.n_elem; ++l) + { + // If l no longer refers to an element in this column, advance the column + // number accordingly. + if (adjacencies(l) >= nextColIndex) + { + curPointInd = (size_t) (adjacencies(l) / atoms); + nextColIndex = (curPointInd + 1) * atoms; + } + + ++neighborCounts(curPointInd); + } + } + + // Handle the case of inactive atoms (atoms not used in the given coding). + std::vector inactiveAtoms; + + for (size_t j = 0; j < atoms; ++j) + { + if (arma::accu(codes.row(j) != 0) == 0) + inactiveAtoms.push_back(j); + } + + const size_t nInactiveAtoms = inactiveAtoms.size(); + const size_t nActiveAtoms = atoms - nInactiveAtoms; + + // Efficient construction of Z restricted to active atoms. + arma::mat matActiveZ; + if (nInactiveAtoms > 0) + { + math::RemoveRows(codes, inactiveAtoms, matActiveZ); + } + + if (nInactiveAtoms > 0) + { + Log::Warn << "There are " << nInactiveAtoms + << " inactive atoms. They will be re-initialized randomly.\n"; + } + + Log::Debug << "Solving Dual via Newton's Method.\n"; + + // Solve using Newton's method in the dual - note that the final dot + // multiplication with inv(A) seems to be unavoidable. Although more + // expensive, the code written this way (we use solve()) should be more + // numerically stable than just using inv(A) for everything. + arma::vec dualVars = arma::zeros(nActiveAtoms); + + // vec dualVars = 1e-14 * ones(nActiveAtoms); + + // Method used by feature sign code - fails miserably here. Perhaps the + // MATLAB optimizer fmincon does something clever? + // vec dualVars = 10.0 * randu(nActiveAtoms, 1); + + // vec dualVars = diagvec(solve(dictionary, data * trans(codes)) + // - codes * trans(codes)); + // for (size_t i = 0; i < dualVars.n_elem; ++i) + // if (dualVars(i) < 0) + // dualVars(i) = 0; + + bool converged = false; + + // If we have any inactive atoms, we must construct these differently. + arma::mat codesXT; + arma::mat codesZT; + + if (inactiveAtoms.empty()) + { + codesXT = codes * trans(data); + codesZT = codes * trans(codes); + } + else + { + codesXT = matActiveZ * trans(data); + codesZT = matActiveZ * trans(matActiveZ); + } + + double normGradient = 0; + double improvement = 0; + for (size_t t = 1; (t != maxIterations) && !converged; ++t) + { + arma::mat A = codesZT + diagmat(dualVars); + + arma::mat matAInvZXT = solve(A, codesXT); + + arma::vec gradient = -arma::sum(arma::square(matAInvZXT), 1); + gradient += 1; + + arma::mat hessian = -(-2 * (matAInvZXT * trans(matAInvZXT)) % inv(A)); + + arma::vec searchDirection = -solve(hessian, gradient); + + // Armijo line search. + const double c = 1e-4; + double alpha = 1.0; + const double rho = 0.9; + double sufficientDecrease = c * dot(gradient, searchDirection); + + // A maxIterations parameter for the Armijo line search may be a good idea, + // but it doesn't seem to be causing any problems for now. + while (true) + { + // Calculate objective. + double sumDualVars = arma::sum(dualVars); + double fOld = -(-trace(trans(codesXT) * matAInvZXT) - sumDualVars); + double fNew = -(-trace(trans(codesXT) * solve(codesZT + + diagmat(dualVars + alpha * searchDirection), codesXT)) - + (sumDualVars + alpha * arma::sum(searchDirection))); + + if (fNew <= fOld + alpha * sufficientDecrease) + { + searchDirection = alpha * searchDirection; + improvement = fOld - fNew; + break; + } + + alpha *= rho; + } + + // Take step and print useful information. + dualVars += searchDirection; + normGradient = arma::norm(gradient, 2); + Log::Debug << "Newton Method iteration " << t << ":" << std::endl; + Log::Debug << " Gradient norm: " << std::scientific << normGradient + << "." << std::endl; + Log::Debug << " Improvement: " << std::scientific << improvement << ".\n"; + + if (normGradient < newtonTolerance) + converged = true; + } + + if (inactiveAtoms.empty()) + { + // Directly update dictionary. + dictionary = trans(solve(codesZT + diagmat(dualVars), codesXT)); + } + else + { + arma::mat activeDictionary = trans(solve(codesZT + + diagmat(dualVars), codesXT)); + + // Update all atoms. + size_t currentInactiveIndex = 0; + for (size_t i = 0; i < atoms; ++i) + { + if (inactiveAtoms[currentInactiveIndex] == i) + { + // This atom is inactive. Reinitialize it randomly. + dictionary.col(i) = (data.col(math::RandInt(data.n_cols)) + + data.col(math::RandInt(data.n_cols)) + + data.col(math::RandInt(data.n_cols))); + + dictionary.col(i) /= arma::norm(dictionary.col(i), 2); + + // Increment inactive index counter. + ++currentInactiveIndex; + } + else + { + // Update estimate. + dictionary.col(i) = activeDictionary.col(i - currentInactiveIndex); + } + } + } + + return normGradient; +} + +// Project each atom of the dictionary back into the unit ball (if necessary). +inline void SparseCoding::ProjectDictionary() +{ + for (size_t j = 0; j < atoms; ++j) + { + double atomNorm = arma::norm(dictionary.col(j), 2); + if (atomNorm > 1) + { + Log::Info << "Norm of atom " << j << " exceeds 1 (" << std::scientific + << atomNorm << "). Shrinking...\n"; + dictionary.col(j) /= atomNorm; + } + } +} + +// Compute the objective function. +inline double SparseCoding::Objective(const arma::mat& data, + const arma::mat& codes) + const +{ + double l11NormZ = arma::sum(arma::sum(arma::abs(codes))); + double froNormResidual = arma::norm(data - (dictionary * codes), "fro"); + + if (lambda2 > 0) + { + double froNormZ = arma::norm(codes, "fro"); + return 0.5 * (std::pow(froNormResidual, 2.0) + (lambda2 * + std::pow(froNormZ, 2.0))) + (lambda1 * l11NormZ); + } + else // It can be simpler. + { + return 0.5 * std::pow(froNormResidual, 2.0) + lambda1 * l11NormZ; + } +} + template double SparseCoding::Train( const arma::mat& data, diff --git a/src/mlpack/methods/svdplusplus/svdplusplus_function_impl.hpp b/src/mlpack/methods/svdplusplus/svdplusplus_function_impl.hpp index e5e0a3ce10..d575848902 100644 --- a/src/mlpack/methods/svdplusplus/svdplusplus_function_impl.hpp +++ b/src/mlpack/methods/svdplusplus/svdplusplus_function_impl.hpp @@ -454,7 +454,7 @@ inline double ParallelSGD::Optimize( if (shuffle) // Determine order of visitation. std::shuffle(visitationOrder.begin(), visitationOrder.end(), - mlpack::math::randGen); + mlpack::math::RandGen()); #pragma omp parallel { diff --git a/src/mlpack/tests/distribution_test.cpp b/src/mlpack/tests/distribution_test.cpp index 99282e07ce..35298ee1d8 100644 --- a/src/mlpack/tests/distribution_test.cpp +++ b/src/mlpack/tests/distribution_test.cpp @@ -669,7 +669,7 @@ TEST_CASE("GammaDistributionTrainTest", "[DistributionTest]") // Random generation of gamma-like points. for (size_t j = 0; j < d; ++j) for (size_t i = 0; i < N; ++i) - rdata(j, i) = dist(math::randGen); + rdata(j, i) = dist(math::RandGen()); // Create Gamma object and call Train() on reference set. GammaDistribution gDist; @@ -687,7 +687,7 @@ TEST_CASE("GammaDistributionTrainTest", "[DistributionTest]") // Random generation of gamma-like points. for (size_t j = 0; j < d2; ++j) for (size_t i = 0; i < N2; ++i) - rdata2(j, i) = dist(math::randGen); + rdata2(j, i) = dist(math::RandGen()); // Fit results using old object. gDist.Train(rdata2); @@ -715,7 +715,7 @@ TEST_CASE("GammaDistributionTrainWithProbabilitiesTest", "[DistributionTest]") for (size_t j = 0; j < d; ++j) for (size_t i = 0; i < N; ++i) - rdata(j, i) = dist(math::randGen); + rdata(j, i) = dist(math::RandGen()); // Fill the probabilities randomly. arma::vec probabilities(N, arma::fill::randu); @@ -759,7 +759,7 @@ TEST_CASE("GammaDistributionTrainAllProbabilities1Test", "[DistributionTest]") for (size_t j = 0; j < d; ++j) for (size_t i = 0; i < N; ++i) - rdata(j, i) = dist(math::randGen); + rdata(j, i) = dist(math::RandGen()); // Fit results with only data. GammaDistribution gDist; @@ -808,9 +808,9 @@ TEST_CASE("GammaDistributionTrainTwoDistProbabilities1Test", for (size_t i = 0; i < N; ++i) { if (i % 2 == 0) - rdata(j, i) = dist(math::randGen); + rdata(j, i) = dist(math::RandGen()); else - rdata(j, i) = dist2(math::randGen); + rdata(j, i) = dist2(math::RandGen()); } } @@ -859,7 +859,7 @@ TEST_CASE("GammaDistributionFittingTest", "[DistributionTest]") arma::mat rdata(d, N); for (size_t j = 0; j < d; ++j) for (size_t i = 0; i < N; ++i) - rdata(j, i) = dist(math::randGen); + rdata(j, i) = dist(math::RandGen()); // Create Gamma object and call Train() on reference set. GammaDistribution gDist; @@ -880,7 +880,7 @@ TEST_CASE("GammaDistributionFittingTest", "[DistributionTest]") arma::mat rdata2(d, N); for (size_t j = 0; j < d; ++j) for (size_t i = 0; i < N; ++i) - rdata2(j, i) = dist2(math::randGen); + rdata2(j, i) = dist2(math::RandGen()); // Create Gamma object and call Train() on reference set. GammaDistribution gDist2;