diff --git a/src/mlpack/core/dists/regression_distribution_impl.hpp b/src/mlpack/core/dists/regression_distribution_impl.hpp index 328f0c4ab2..0634549e8b 100644 --- a/src/mlpack/core/dists/regression_distribution_impl.hpp +++ b/src/mlpack/core/dists/regression_distribution_impl.hpp @@ -63,8 +63,8 @@ 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()); + rf.Predict(observation.rows(1, observation.n_rows - 1), fitted); + return err.Probability(observation(0) - fitted.t()); } inline void RegressionDistribution::Predict(const arma::mat& points, diff --git a/src/mlpack/methods/linear_regression/linear_regression.hpp b/src/mlpack/methods/linear_regression/linear_regression.hpp index c2d7ce2e2b..7fe0bff132 100644 --- a/src/mlpack/methods/linear_regression/linear_regression.hpp +++ b/src/mlpack/methods/linear_regression/linear_regression.hpp @@ -71,14 +71,90 @@ class LinearRegression * regularization parameter lambda, call Lambda() or set a different value in * the constructor. * + * This version of `Train()` is deprecated and will be removed in mlpack + * 5.0.0. Use the version of `Train()` that specifies `lambda` before + * `intercept` instead. + * * @param predictors X, the matrix of data points to train the model on. * @param responses y, the responses to the data points. * @param intercept Whether or not to fit an intercept term. * @return The least squares error after training. */ + mlpack_deprecated /** Will be removed in mlpack 5.0.0. */ double Train(const arma::mat& predictors, const arma::rowvec& responses, - const bool intercept = true); + const bool intercept); + + /** + * Train the LinearRegression model on the given data and weights. Careful! + * This will completely ignore and overwrite the existing model. This + * particular implementation does not have an incremental training algorithm. + * To set the regularization parameter lambda, call Lambda() or set a + * different value in the constructor. + * + * This version of `Train()` is deprecated and will be removed in mlpack + * 5.0.0. Use the version of `Train()` that specifies `lambda` before + * `intercept` instead. + * + * @param predictors X, the matrix of data points to train the model on. + * @param responses y, the responses to the data points. + * @param weights Observation weights (for boosting). + * @param intercept Whether or not to fit an intercept term. + * @return The least squares error after training. + */ + mlpack_deprecated /** Will be removed in mlpack 5.0.0. */ + double Train(const arma::mat& predictors, + const arma::rowvec& responses, + const arma::rowvec& weights, + const bool intercept); + + /** + * Train the LinearRegression model on the given data and weights. Careful! + * This will completely ignore and overwrite the existing model. This + * particular implementation does not have an incremental training algorithm. + * To set the regularization parameter lambda, call Lambda() or set a + * different value in the constructor. + * + * @param predictors X, the matrix of data points to train the model on. + * @param responses y, the responses to the data points. + * @return The least squares error after training. + */ + double Train(const arma::mat& predictors, + const arma::rowvec& responses); + + /** + * Train the LinearRegression model on the given data and weights. Careful! + * This will completely ignore and overwrite the existing model. This + * particular implementation does not have an incremental training algorithm. + * To set the regularization parameter lambda, call Lambda() or set a + * different value in the constructor. + * + * @param predictors X, the matrix of data points to train the model on. + * @param responses y, the responses to the data points. + * @param lambda L2 regularization penalty parameter to use. + * @return The least squares error after training. + */ + double Train(const arma::mat& predictors, + const arma::rowvec& responses, + const double lambda); + + /** + * Train the LinearRegression model on the given data and weights. Careful! + * This will completely ignore and overwrite the existing model. This + * particular implementation does not have an incremental training algorithm. + * To set the regularization parameter lambda, call Lambda() or set a + * different value in the constructor. + * + * @param predictors X, the matrix of data points to train the model on. + * @param responses y, the responses to the data points. + * @param lambda L2 regularization penalty parameter to use. + * @param intercept Whether or not to fit an intercept term. + * @return The least squares error after training. + */ + double Train(const arma::mat& predictors, + const arma::rowvec& responses, + const double lambda, + const bool intercept); /** * Train the LinearRegression model on the given data and weights. Careful! @@ -90,13 +166,49 @@ class LinearRegression * @param predictors X, the matrix of data points to train the model on. * @param responses y, the responses to the data points. * @param weights Observation weights (for boosting). + * @return The least squares error after training. + */ + double Train(const arma::mat& predictors, + const arma::rowvec& responses, + const arma::rowvec& weights); + + /** + * Train the LinearRegression model on the given data and weights. Careful! + * This will completely ignore and overwrite the existing model. This + * particular implementation does not have an incremental training algorithm. + * To set the regularization parameter lambda, call Lambda() or set a + * different value in the constructor. + * + * @param predictors X, the matrix of data points to train the model on. + * @param responses y, the responses to the data points. + * @param weights Observation weights (for boosting). + * @param lambda L2 regularization penalty parameter to use. + * @return The least squares error after training. + */ + double Train(const arma::mat& predictors, + const arma::rowvec& responses, + const arma::rowvec& weights, + const double lambda); + + /** + * Train the LinearRegression model on the given data and weights. Careful! + * This will completely ignore and overwrite the existing model. This + * particular implementation does not have an incremental training algorithm. + * To set the regularization parameter lambda, call Lambda() or set a + * different value in the constructor. + * + * @param predictors X, the matrix of data points to train the model on. + * @param responses y, the responses to the data points. + * @param weights Observation weights (for boosting). + * @param lambda L2 regularization penalty parameter to use. * @param intercept Whether or not to fit an intercept term. * @return The least squares error after training. */ double Train(const arma::mat& predictors, const arma::rowvec& responses, const arma::rowvec& weights, - const bool intercept = true); + const double lambda, + const bool intercept); /** * Calculate y_i for each data point in points. diff --git a/src/mlpack/methods/linear_regression/linear_regression_impl.hpp b/src/mlpack/methods/linear_regression/linear_regression_impl.hpp index fe79f2ed55..3980b9d881 100644 --- a/src/mlpack/methods/linear_regression/linear_regression_impl.hpp +++ b/src/mlpack/methods/linear_regression/linear_regression_impl.hpp @@ -37,18 +37,67 @@ inline LinearRegression::LinearRegression( Train(predictors, responses, weights, intercept); } +mlpack_deprecated /** Will be removed in mlpack 5.0.0. */ inline double LinearRegression::Train(const arma::mat& predictors, const arma::rowvec& responses, const bool intercept) { - return Train(predictors, responses, arma::rowvec(), intercept); + return Train(predictors, responses, arma::rowvec(), this->lambda, intercept); } +mlpack_deprecated /** Will be removed in mlpack 5.0.0. */ inline double LinearRegression::Train(const arma::mat& predictors, const arma::rowvec& responses, const arma::rowvec& weights, const bool intercept) { + return Train(predictors, responses, weights, this->lambda, intercept); +} + +inline double LinearRegression::Train(const arma::mat& predictors, + const arma::rowvec& responses) +{ + return Train(predictors, responses, arma::rowvec(), this->lambda, + this->intercept); +} + +inline double LinearRegression::Train(const arma::mat& predictors, + const arma::rowvec& responses, + const double lambda) +{ + return Train(predictors, responses, arma::rowvec(), lambda, this->intercept); +} + +inline double LinearRegression::Train(const arma::mat& predictors, + const arma::rowvec& responses, + const double lambda, + const bool intercept) +{ + return Train(predictors, responses, arma::rowvec(), lambda, intercept); +} + +inline double LinearRegression::Train(const arma::mat& predictors, + const arma::rowvec& responses, + const arma::rowvec& weights) +{ + return Train(predictors, responses, weights, this->lambda, this->intercept); +} + +inline double LinearRegression::Train(const arma::mat& predictors, + const arma::rowvec& responses, + const arma::rowvec& weights, + const double lambda) +{ + return Train(predictors, responses, weights, lambda, this->intercept); +} + +inline double LinearRegression::Train(const arma::mat& predictors, + const arma::rowvec& responses, + const arma::rowvec& weights, + const double lambda, + const bool intercept) +{ + this->lambda = lambda; this->intercept = intercept; /* @@ -106,7 +155,7 @@ inline void LinearRegression::Predict( // Prevent underflow. const size_t labels = (parameters.n_rows == 0) ? size_t(0) : size_t(parameters.n_rows - 1); - util::CheckSameDimensionality(points, labels, "LinearRegression::Predict()", + util::CheckSameDimensionality(points, labels, "LinearRegression::Predict()", "points"); // Get the predictions, but this ignores the intercept value // (parameters[0]). @@ -119,7 +168,7 @@ inline void LinearRegression::Predict( { // We want to be sure we have the correct number of dimensions in // the dataset. - util::CheckSameDimensionality(points, parameters, + util::CheckSameDimensionality(points, parameters, "LinearRegression::Predict()", "points"); predictions = arma::trans(parameters) * points; } @@ -131,7 +180,7 @@ inline double LinearRegression::ComputeError( { // Sanity check on data. util::CheckSameSizes(predictors, responses, "LinearRegression::Train()"); - + // Get the number of columns and rows of the dataset. const size_t nCols = predictors.n_cols; const size_t nRows = predictors.n_rows; diff --git a/src/mlpack/tests/linear_regression_test.cpp b/src/mlpack/tests/linear_regression_test.cpp index 8bbf9dc7d6..bd12dc18c8 100644 --- a/src/mlpack/tests/linear_regression_test.cpp +++ b/src/mlpack/tests/linear_regression_test.cpp @@ -265,3 +265,58 @@ TEST_CASE("LinearRegressionTrainReturnObjective", "[LinearRegressionTest]") REQUIRE(std::isfinite(error) == true); } + +/** + * Make sure all versions of Train() work correctly. + */ +TEST_CASE("LinearRegressionAllTrainVersionsTest", "[LinearRegressionTest]") +{ + // The data doesn't really matter for this test; mostly we want to make sure + // that all the Train() variants work properly. + arma::mat predictors; + predictors = { { 0, 1, 2, 4, 8, 16 }, + { 16, 8, 4, 2, 1, 0 } }; + arma::rowvec responses = "0 2 4 3 8 8"; + arma::rowvec weights = "1.0 1.1 1.2 0.8 0.9 1.0"; + + LinearRegression lr1, lr2, lr3, lr4, lr5, lr6; + + lr1.Train(predictors, responses); + lr2.Train(predictors, responses, 0.1); + lr3.Train(predictors, responses, 0.2, false); + lr4.Train(predictors, responses, weights); + lr5.Train(predictors, responses, weights, 0.3); + lr6.Train(predictors, responses, weights, 0.4, false); + + // We don't care about the specifics of the trained model, but we want to just + // make sure everything appears to be correct from the sizes and + // hyperparameters. + REQUIRE(lr1.Lambda() == Approx(0.0).margin(1e-10)); + REQUIRE(lr1.Intercept() == true); + REQUIRE(lr1.Parameters().n_elem == 3); + + REQUIRE(lr2.Lambda() == Approx(0.1).margin(1e-10)); + REQUIRE(lr2.Intercept() == true); + REQUIRE(lr2.Parameters().n_elem == 3); + + REQUIRE(lr3.Lambda() == Approx(0.2).margin(1e-10)); + REQUIRE(lr3.Intercept() == false); + REQUIRE(lr3.Parameters().n_elem == 2); + + REQUIRE(lr4.Lambda() == Approx(0.0).margin(1e-10)); + REQUIRE(lr4.Intercept() == true); + REQUIRE(lr4.Parameters().n_elem == 3); + + REQUIRE(lr5.Lambda() == Approx(0.3).margin(1e-10)); + REQUIRE(lr5.Intercept() == true); + REQUIRE(lr5.Parameters().n_elem == 3); + + REQUIRE(lr6.Lambda() == Approx(0.4).margin(1e-10)); + REQUIRE(lr6.Intercept() == false); + REQUIRE(lr6.Parameters().n_elem == 2); + + // We can also check that the weighted model is different from the unweighted + // model. + REQUIRE(!arma::approx_equal(lr1.Parameters(), lr4.Parameters(), "absdiff", + 1e-5)); +}