diff --git a/src/mlpack/methods/ann/layer/CMakeLists.txt b/src/mlpack/methods/ann/layer/CMakeLists.txt index 1cf544f3f4..1990f693a2 100644 --- a/src/mlpack/methods/ann/layer/CMakeLists.txt +++ b/src/mlpack/methods/ann/layer/CMakeLists.txt @@ -18,6 +18,8 @@ set(SOURCES concat_impl.hpp concat_performance.hpp concat_performance_impl.hpp + concatenate.hpp + concatenate_impl.hpp constant.hpp constant_impl.hpp convolution.hpp diff --git a/src/mlpack/methods/ann/layer/concatenate.hpp b/src/mlpack/methods/ann/layer/concatenate.hpp new file mode 100644 index 0000000000..77fd985cf4 --- /dev/null +++ b/src/mlpack/methods/ann/layer/concatenate.hpp @@ -0,0 +1,120 @@ +/** + * @file concatenate.hpp + * @author Atharva Khandait + * + * Definition of the Concatenate class that concatenate a constant matrix to + * the incoming data. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_ANN_LAYER_CONCATENATE_HPP +#define MLPACK_METHODS_ANN_LAYER_CONCATENATE_HPP + +#include +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +/** + * Implementation of the Concatenate module class. The Concatenate module + * concatenates a constant given matrix to the incoming data. + * Note: Users need to use the Concat() function to provide the concat matrix. + * + * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + * @tparam OutputDataType Type of the output data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + */ +template < + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +class Concatenate +{ + public: + /** + * Create the Concatenate object using the specified number of output units. + */ + Concatenate(); + + /** + * Ordinary feed forward pass of a neural network, evaluating the function + * f(x) by propagating the activity forward through f. + * + * @param input Input data used for evaluating the specified function. + * @param output Resulting output activation. + */ + template + void Forward(const arma::Mat&& input, arma::Mat&& output); + + /** + * Ordinary feed backward pass of a neural network, calculating the function + * f(x) by propagating x backwards trough f. Using the results from the feed + * forward pass. + * + * @param input The propagated input activation. + * @param gy The backpropagated error. + * @param g The calculated gradient. + */ + template + void Backward(const arma::Mat&& /* input */, + const arma::Mat&& gy, + arma::Mat&& g); + + //! Get the parameters. + OutputDataType const& Parameters() const { return weights; } + //! Modify the parameters. + OutputDataType& Parameters() { return weights; } + + //! Get the output parameter. + OutputDataType const& OutputParameter() const { return outputParameter; } + //! Modify the output parameter. + OutputDataType& OutputParameter() { return outputParameter; } + + //! Get the delta. + OutputDataType const& Delta() const { return delta; } + //! Modify the delta. + OutputDataType& Delta() { return delta; } + + //! Get the concat matrix. + OutputDataType const& Concat() const { return concat; } + //! Modify the delta. + OutputDataType& Concat() { return concat; } + + /** + * Serialize the layer + */ + template + void serialize(Archive& /* ar */, const unsigned int /* version */) + { + // Nothing to do here. + } + + private: + //! Locally-stored number of input rows. + size_t inRows; + + //! Locally-stored weight object. + OutputDataType weights; + + //! Locally-stored delta object. + OutputDataType delta; + + //! Locally-stored output parameter object. + OutputDataType outputParameter; + + //! Locally-stored matrix to be concatenated to input. + OutputDataType concat; +}; // class Concatenate + +} // namespace ann +} // namespace mlpack + +// Include implementation. +#include "concatenate_impl.hpp" + +#endif diff --git a/src/mlpack/methods/ann/layer/concatenate_impl.hpp b/src/mlpack/methods/ann/layer/concatenate_impl.hpp new file mode 100644 index 0000000000..c1e3139f67 --- /dev/null +++ b/src/mlpack/methods/ann/layer/concatenate_impl.hpp @@ -0,0 +1,59 @@ +/** + * @file concatenate_impl.hpp + * @author Atharva Khandait + * + * Implementation of the Concatenate class that concatenates a constant matrix to + * the incoming data. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_ANN_LAYER_CONCATENATE_IMPL_HPP +#define MLPACK_METHODS_ANN_LAYER_CONCATENATE_IMPL_HPP + +// In case it hasn't yet been included. +#include "concatenate.hpp" + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +template +Concatenate::Concatenate() +{ + // Nothing to do here. +} + +template +template +void Concatenate::Forward( + const arma::Mat&& input, arma::Mat&& output) +{ + if (concat.is_empty()) + Log::Warn << "The concat matrix has not been provided." << std::endl; + + if (input.n_cols != concat.n_cols) + { + Log::Fatal << "The number of columns of the concat matrix should be equal " + << "to the number of columns of input matrix." << std::endl; + } + + inRows = input.n_rows; + output = arma::join_cols(input, concat); +} + +template +template +void Concatenate::Backward( + const arma::Mat&& /* input */, + const arma::Mat&& gy, + arma::Mat&& g) +{ + g = gy.submat(0, 0, inRows - 1, concat.n_cols - 1); +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/layer/layer_types.hpp b/src/mlpack/methods/ann/layer/layer_types.hpp index c2a422c830..e64da95435 100644 --- a/src/mlpack/methods/ann/layer/layer_types.hpp +++ b/src/mlpack/methods/ann/layer/layer_types.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,7 @@ template class LSTM; template class GRU; template class FastLSTM; template class VRClassReward; +template class Concatenate; template*, BilinearInterpolation*, Concat*, + Concatenate*, ConcatPerformance, arma::mat, arma::mat>*, Constant*, diff --git a/src/mlpack/methods/ann/layer/reparametrization_impl.hpp b/src/mlpack/methods/ann/layer/reparametrization_impl.hpp index 02721fa5bc..21944725d3 100644 --- a/src/mlpack/methods/ann/layer/reparametrization_impl.hpp +++ b/src/mlpack/methods/ann/layer/reparametrization_impl.hpp @@ -81,7 +81,7 @@ void Reparametrization::Backward( if (includeKl) { g = join_cols(gy % std::move(gaussianSample) % g + (-1 / stdDev + stdDev) - % g * beta, gy + mean * beta); + % g * beta, gy + mean * beta / mean.n_cols); } else g = join_cols(gy % std::move(gaussianSample) % g, gy); diff --git a/src/mlpack/methods/ann/loss_functions/mean_squared_error_impl.hpp b/src/mlpack/methods/ann/loss_functions/mean_squared_error_impl.hpp index f8d557be0b..82c9cc3bd9 100644 --- a/src/mlpack/methods/ann/loss_functions/mean_squared_error_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/mean_squared_error_impl.hpp @@ -39,7 +39,7 @@ void MeanSquaredError::Backward( const TargetType&& target, OutputType&& output) { - output = (input - target); + output = 2 * (input - target) / target.n_cols; } template diff --git a/src/mlpack/tests/ann_layer_test.cpp b/src/mlpack/tests/ann_layer_test.cpp index 440223b40b..ef7500bc4c 100644 --- a/src/mlpack/tests/ann_layer_test.cpp +++ b/src/mlpack/tests/ann_layer_test.cpp @@ -1083,6 +1083,77 @@ BOOST_AUTO_TEST_CASE(GradientConcatLayerTest) BOOST_REQUIRE_LE(CheckGradient(function), 1e-4); } +/** + * Simple concatenate module test. + */ +BOOST_AUTO_TEST_CASE(SimpleConcatenateLayerTest) +{ + arma::mat input = arma::ones(5, 1); + arma::mat output, delta; + + Concatenate<> module; + module.Concat() = arma::ones(5, 1) * 0.5; + + // Test the Forward function. + module.Forward(std::move(input), std::move(output)); + + BOOST_REQUIRE_EQUAL(arma::accu(output), 7.5); + + // Test the Backward function. + module.Backward(std::move(input), std::move(output), std::move(delta)); + BOOST_REQUIRE_EQUAL(arma::accu(delta), 5); +} + +/** + * Concatenate layer numerical gradient test. + */ +BOOST_AUTO_TEST_CASE(GradientConcatenateLayerTest) +{ + // Concatenate function gradient instantiation. + struct GradientFunction + { + GradientFunction() + { + input = arma::randu(10, 1); + target = arma::mat("1"); + + model = new FFN, NguyenWidrowInitialization>(); + model->Predictors() = input; + model->Responses() = target; + model->Add >(); + model->Add >(10, 5); + + arma::mat concat = arma::ones(5, 1); + concatenate = new Concatenate<>(); + concatenate->Concat() = concat; + model->Add(concatenate); + + model->Add >(10, 5); + model->Add >(); + } + + ~GradientFunction() + { + delete model; + } + + double Gradient(arma::mat& gradient) const + { + double error = model->Evaluate(model->Parameters(), 0, 1); + model->Gradient(model->Parameters(), 0, gradient, 1); + return error; + } + + arma::mat& Parameters() { return model->Parameters(); } + + FFN, NguyenWidrowInitialization>* model; + Concatenate<>* concatenate; + arma::mat input, target; + } function; + + BOOST_REQUIRE_LE(CheckGradient(function), 1e-4); +} + /** * Simple lookup module test. */ @@ -1922,8 +1993,8 @@ BOOST_AUTO_TEST_CASE(GradientReparametrizationLayerBetaTest) { GradientFunction() { - input = arma::randu(10, 1); - target = arma::mat("1"); + input = arma::randu(10, 2); + target = arma::mat("1 1"); model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; diff --git a/src/mlpack/tests/loss_functions_test.cpp b/src/mlpack/tests/loss_functions_test.cpp index 13082e9a2c..797e553d3e 100644 --- a/src/mlpack/tests/loss_functions_test.cpp +++ b/src/mlpack/tests/loss_functions_test.cpp @@ -107,8 +107,10 @@ BOOST_AUTO_TEST_CASE(SimpleMeanSquaredErrorTest) // Test the Backward function. module.Backward(std::move(input), std::move(target), std::move(output)); - // We subtract a zero vector, so the output should be equal with the input. - CheckMatrices(input, output); + // We subtract a zero vector, so according to the used backward formula: + // output = 2 * (input - target) / target.n_cols, + // output * nofColumns / 2 should be equal to input. + CheckMatrices(input, output * output.n_cols / 2); BOOST_REQUIRE_EQUAL(output.n_rows, input.n_rows); BOOST_REQUIRE_EQUAL(output.n_cols, input.n_cols); @@ -121,7 +123,7 @@ BOOST_AUTO_TEST_CASE(SimpleMeanSquaredErrorTest) // Test the Backward function on a single input. module.Backward(std::move(input), std::move(target), std::move(output)); // Test whether the output is negative. - BOOST_REQUIRE_EQUAL(arma::accu(output), -1); + BOOST_REQUIRE_EQUAL(arma::accu(output), -2); BOOST_REQUIRE_EQUAL(output.n_elem, 1); } @@ -266,6 +268,49 @@ BOOST_AUTO_TEST_CASE(SimpleEarthMoverDistanceLayerTest) BOOST_REQUIRE_EQUAL(output.n_cols, input2.n_cols); } +/* + * Mean Squared Error numerical gradient test. + */ +BOOST_AUTO_TEST_CASE(GradientMeanSquaredErrorTest) +{ + // Linear function gradient instantiation. + struct GradientFunction + { + GradientFunction() + { + input = arma::randu(10, 1); + target = arma::randu(2, 1); + + model = new FFN, NguyenWidrowInitialization>(); + model->Predictors() = input; + model->Responses() = target; + model->Add >(); + model->Add >(10, 2); + model->Add >(); + } + + ~GradientFunction() + { + delete model; + } + + double Gradient(arma::mat& gradient) const + { + arma::mat output; + double error = model->Evaluate(model->Parameters(), 0, 1); + model->Gradient(model->Parameters(), 0, gradient, 1); + return error; + } + + arma::mat& Parameters() { return model->Parameters(); } + + FFN, NguyenWidrowInitialization>* model; + arma::mat input, target; + } function; + + BOOST_REQUIRE_LE(CheckGradient(function), 1e-4); +} + /* * Reconstruction Loss numerical gradient test. */