diff --git a/src/mlpack/methods/ann/layer/CMakeLists.txt b/src/mlpack/methods/ann/layer/CMakeLists.txt index e69e65e23d..3ed593a931 100644 --- a/src/mlpack/methods/ann/layer/CMakeLists.txt +++ b/src/mlpack/methods/ann/layer/CMakeLists.txt @@ -6,6 +6,8 @@ set(SOURCES alpha_dropout.hpp alpha_dropout_impl.hpp base_layer.hpp + concat.hpp + concat_impl.hpp concatenate.hpp concatenate_impl.hpp convolution.hpp diff --git a/src/mlpack/methods/ann/layer/not_adapted/concat.hpp b/src/mlpack/methods/ann/layer/concat.hpp similarity index 74% rename from src/mlpack/methods/ann/layer/not_adapted/concat.hpp rename to src/mlpack/methods/ann/layer/concat.hpp index 1d99ff60c9..7d8aac0f3d 100644 --- a/src/mlpack/methods/ann/layer/not_adapted/concat.hpp +++ b/src/mlpack/methods/ann/layer/concat.hpp @@ -25,42 +25,47 @@ namespace ann /** Artificial Neural Network. */ { * feed-forward fully connected network container which plugs various layers * together. * - * @tparam InputType Type of the input data (arma::colvec, arma::mat, - * arma::sp_mat or arma::cube). - * @tparam OutputType Type of the output data (arma::colvec, arma::mat, - * arma::sp_mat or arma::cube). + * NOTE: this class is not intended to exist for long! It will be replaced with + * a more flexible DAG network type. + * + * @tparam MatType Matrix representation to accept as input and use for + * computation. */ -template < - typename InputType = arma::mat, - typename OutputType = arma::mat -> -class ConcatType : public MultiLayer +template +class ConcatType : public MultiLayer { public: /** - * Create the Concat object using the specified parameters. - * - * @param run Call the Forward/Backward method before the output is merged. + * Create the Concat object. The axis used for concatenation will be the last + * one. */ - ConcatType(const bool run = true); + ConcatType(); /** * Create the Concat object, specifying a particular axis on which the layer * outputs should be concatenated. * * @param axis Concat axis. - * @param run Call the Forward/Backward method before the output is merged. */ - ConcatType(const size_t axis, const bool run = true); + ConcatType(const size_t axis); /** * Destroy the layers held by the model. */ - ~ConcatType(); + virtual ~ConcatType(); //! Clone the ConcatType object. This handles polymorphism correctly. ConcatType* Clone() const { return new ConcatType(*this); } + //! Copy the given ConcatType layer. + ConcatType(const ConcatType& other); + //! Take ownership of the given ConcatType layer. + ConcatType(ConcatType&& other); + //! Copy the given ConcatType layer. + ConcatType& operator=(const ConcatType& other); + //! Take ownership of the given ConcatType layer. + ConcatType& operator=(ConcatType&& other); + /** * Ordinary feed forward pass of a neural network, evaluating the function * f(x) by propagating the activity forward through f. @@ -68,7 +73,7 @@ class ConcatType : public MultiLayer * @param input Input data used for evaluating the specified function. * @param output Resulting output activation. */ - void Forward(const InputType& input, OutputType& output); + void Forward(const MatType& input, MatType& output); /** * Ordinary feed backward pass of a neural network, using 3rd-order tensors as @@ -79,9 +84,9 @@ class ConcatType : public MultiLayer * @param gy The backpropagated error. * @param g The calculated gradient. */ - void Backward(const InputType& /* input */, - const OutputType& gy, - OutputType& g); + void Backward(const MatType& /* input */, + const MatType& gy, + MatType& g); /** * This is the overload of Backward() that runs only a specific layer with @@ -92,9 +97,9 @@ class ConcatType : public MultiLayer * @param g The calculated gradient. * @param index The index of the layer to run. */ - void Backward(const InputType& /* input */, - const OutputType& gy, - OutputType& g, + void Backward(const MatType& /* input */, + const MatType& gy, + MatType& g, const size_t index); /** @@ -104,9 +109,9 @@ class ConcatType : public MultiLayer * @param error The calculated error. * @param gradient The calculated gradient. */ - void Gradient(const InputType& /* input */, - const OutputType& error, - OutputType& /* gradient */); + void Gradient(const MatType& /* input */, + const MatType& error, + MatType& /* gradient */); /** * This is the overload of Gradient() that runs a specific layer with the @@ -117,29 +122,24 @@ class ConcatType : public MultiLayer * @param gradient The calculated gradient. * @param The index of the layer to run. */ - void Gradient(const InputType& input, - const OutputType& error, - OutputType& gradient, + void Gradient(const MatType& input, + const MatType& error, + MatType& gradient, const size_t index); - //! Get the value of run parameter. - bool Run() const { return run; } - //! Modify the value of run parameter. - bool& Run() { return run; } - //! Get the axis of concatenation. - const size_t& ConcatAxis() const { return axis; } + const size_t& Axis() const { return axis; } - //! Get the size of the weight matrix. - size_t WeightSize() const { return 0; } + // We don't need to overload WeightSize(); MultiLayer already computes this + // correctly. (It is the sum of weights of all child layers.) void ComputeOutputDimensions() { // The input is sent to every layer. - for (size_t i = 0; i < network.size(); ++i) + for (size_t i = 0; i < this->network.size(); ++i) { - network[i]->InputDimensions() = this->inputDimensions; - network[i]->ComputeOutputDimensions(); + this->network[i]->InputDimensions() = this->inputDimensions; + this->network[i]->ComputeOutputDimensions(); } // If the user did not specify an axis, we will use the last one. @@ -147,7 +147,7 @@ class ConcatType : public MultiLayer // concatenating along is valid. if (!useAxis) { - axis = this->inputDimensions.size() - 1; + axis = this->inputDimensions.size() - 1; } else if (axis >= this->inputDimensions.size()) { @@ -197,10 +197,10 @@ class ConcatType : public MultiLayer } /** - * Serialize the layer + * Serialize the layer. */ template - void serialize(Archive& ar, const uint32_t /* version */); + void serialize(Archive& ar, const uint32_t /* version */); private: //! Parameter which indicates the axis of concatenation. @@ -211,7 +211,7 @@ class ConcatType : public MultiLayer }; // class ConcatType. // Standard Concat layer. -typedef ConcatType Concat; +typedef ConcatType Concat; } // namespace ann } // namespace mlpack diff --git a/src/mlpack/methods/ann/layer/not_adapted/concat_impl.hpp b/src/mlpack/methods/ann/layer/concat_impl.hpp similarity index 53% rename from src/mlpack/methods/ann/layer/not_adapted/concat_impl.hpp rename to src/mlpack/methods/ann/layer/concat_impl.hpp index 7e231b5f81..29c345492b 100644 --- a/src/mlpack/methods/ann/layer/not_adapted/concat_impl.hpp +++ b/src/mlpack/methods/ann/layer/concat_impl.hpp @@ -19,38 +19,81 @@ namespace mlpack { namespace ann /** Artificial Neural Network. */ { -template -ConcatType::ConcatType( - const bool run) : - axis(0), - useAxis(false) -{ - // Nothing to do. -} - -template -ConcatType::ConcatType( - const size_t axis, - const bool run) : +template +ConcatType::ConcatType( + const size_t axis) : + MultiLayer(), axis(axis), useAxis(true) { // Nothing to do. } -template -ConcatType::~ConcatType() +template +ConcatType::ConcatType() : + MultiLayer(), + axis(0), + useAxis(false) { - // Clear memory. - for (size_t i = 0; i < this->network.size(); ++i) - delete this->network[i]; + // Nothing to do. } -template -void ConcatType::Forward( - const InputType& input, OutputType& output) +template +ConcatType::~ConcatType() { - this->InitializeForwardPassMemory(); + // Nothing to do: the child layer memory is already cleared by MultiLayer. +} + +template +ConcatType::ConcatType(const ConcatType& other) : + MultiLayer(other), + axis(other.axis), + useAxis(other.useAxis) +{ + // Nothing else to do. +} + +template +ConcatType::ConcatType(ConcatType&& other) : + MultiLayer(std::move(other)), + axis(std::move(other.axis)), + useAxis(std::move(other.useAxis)) +{ + // Nothing else to do. +} + +template +ConcatType& ConcatType::operator=(const ConcatType& other) +{ + if (this != &other) + { + MultiLayer::operator=(other); + axis = other.axis; + useAxis = other.useAxis; + } + + return *this; +} + +template +ConcatType& ConcatType::operator=(ConcatType&& other) +{ + if (this != &other) + { + MultiLayer::operator=(std::move(other)); + axis = std::move(other.axis); + useAxis = std::move(other.useAxis); + } + + return *this; +} + +template +void ConcatType::Forward(const MatType& input, MatType& output) +{ + // The implementation of MultiLayer is fine: this will allocate a matrix that + // is able to hold each child layer's output. + this->InitializeForwardPassMemory(input.n_cols); // Pass the input through all the layers in the network. for (size_t i = 0; i < this->network.size(); ++i) @@ -76,32 +119,42 @@ void ConcatType::Forward( std::accumulate(this->outputDimensions.begin() + axis + 1, this->outputDimensions.end(), 0); - std::vector> layerOutputAliases; + std::vector> layerOutputAliases( + this->layerOutputs.size()); for (size_t i = 0; i < this->layerOutputs.size(); ++i) { - layerOutputAliases.emplace_back(arma::Cube( - this->layerOutputs[i].memptr(), rows, - this->network[i]->OutputDimensions()[axis], slices, false, true); + MakeAlias(layerOutputAliases.back(), + (typename MatType::elem_type*) this->layerOutputs[i].memptr(), + rows, + this->network[i]->OutputDimensions()[axis], + slices); } - arma::Cube output(output.memptr(), rows, - this->outputDimensions[axis], slices, false, true); + arma::Cube outputAlias; + MakeAlias(outputAlias, + (typename MatType::elem_type*) output.memptr(), + rows, + this->outputDimensions[axis], + slices); // Now get the columns from each output. size_t startCol = 0; for (size_t i = 0; i < layerOutputAliases.size(); ++i) { const size_t cols = layerOutputAliases[i].n_cols; - output.cols(startCol, startCol + cols - 1) = layerOutputAliases[i]; + outputAlias.cols(startCol, startCol + cols - 1) = layerOutputAliases[i]; startCol += cols; } } -template -void ConcatType::Backward( - const InputType& /* input */, const OutputType& gy, OutputType& g) +template +void ConcatType::Backward( + const MatType& /* input */, const MatType& gy, MatType& g) { - this->InitializeBackwardPassMemory(); + // The implementation of MultiLayer is fine: this will allocate a matrix that + // is able to hold each child layer's delta (which has the same size as the + // input). + this->InitializeBackwardPassMemory(gy.n_cols); // Just like the forward pass, we can treat our inputs as a cube, but here we // have to distribute the correct parts of `gy` to the layers. @@ -113,18 +166,20 @@ void ConcatType::Backward( std::accumulate(this->outputDimensions.begin() + axis + 1, this->outputDimensions.end(), 0); - arma::Cube gyTmp(gy.memptr(), rows, - this->outputDimensions[axis], slices, false, true); + arma::Cube gyTmp; + MakeAlias(gyTmp, + (typename MatType::elem_type*) gy.memptr(), + rows, + this->outputDimensions[axis], + slices); size_t startCol = 0; for (size_t i = 0; i < this->network.size(); ++i) { const size_t cols = this->network[i]->OutputDimensions()[axis]; - // TODO: is delta size correct? - // TODO: no copy! - OutputType delta = gyTmp.cols(startCol, startCol + cols - 1); - // TODO: consider batch size correctly - delta.reshape( ... ); + MatType delta = gyTmp.cols(startCol, startCol + cols - 1); + // Reshape so that the batch size is the number of columns. + delta.reshape(delta.n_elem / gy.n_cols, gy.n_cols); this->network[i]->Backward(this->layerOutputs[i], delta, this->layerDeltas[i]); @@ -138,11 +193,11 @@ void ConcatType::Backward( } } -template -void ConcatType::Backward( - const InputType& /* input */, - const OutputType& gy, - OutputType& g, +template +void ConcatType::Backward( + const MatType& /* input */, + const MatType& gy, + MatType& g, const size_t index) { // We only intend to perform a backward pass on one layer. @@ -156,8 +211,12 @@ void ConcatType::Backward( std::accumulate(this->outputDimensions.begin() + axis + 1, this->outputDimensions.end(), 0); - arma::Cube gyTmp(gy.memptr(), rows, - this->outputDimensions[axis], slices, false, true); + arma::Cube gyTmp; + MakeAlias(gyTmp, + (typename MatType::elem_type*) gy.memptr(), + rows, + this->outputDimensions[axis], + slices); size_t startCol = 0; for (size_t i = 0; i < index; ++i) @@ -165,22 +224,22 @@ void ConcatType::Backward( startCol += this->network[i]->OutputDimensions()[axis]; } - // TODO: no copy! const size_t cols = this->network[index]->OutputDimensions()[axis]; - OutputType delta = gyTmp.cols(startCol, startCol + cols - 1); - delta.reshape( ... ); + MatType delta = gyTmp.cols(startCol, startCol + cols - 1); + // Reshape so that the batch size is the number of columns. + delta.reshape(delta.n_elem / gy.n_cols, gy.n_cols); this->network[index]->Backward(this->layerOutputs[index], delta, g); } -template -void ConcatType::Gradient( - const InputType& input, - const OutputType& error, - OutputType& gradient) +template +void ConcatType::Gradient( + const MatType& input, + const MatType& error, + MatType& gradient) { // Just like the forward pass, we can treat our inputs as a cube, but here we - // have to distribute the correct parts of `gy` to the layers. + // have to distribute the correct parts of `error` to the layers. size_t slices = (axis == 0) ? input.n_cols : std::accumulate(this->outputDimensions.begin(), @@ -189,8 +248,12 @@ void ConcatType::Gradient( std::accumulate(this->outputDimensions.begin() + axis + 1, this->outputDimensions.end(), 0); - arma::Cube errorTmp(error.memptr(), rows, - this->outputDimensions[axis], slices, false, true); + arma::Cube errorTmp; + MakeAlias(errorTmp, + (typename MatType::elem_type*) error.memptr(), + rows, + this->outputDimensions[axis], + slices); size_t startCol = 0; size_t startParam = 0; @@ -199,11 +262,13 @@ void ConcatType::Gradient( const size_t cols = this->network[i]->OutputDimensions()[axis]; const size_t params = this->network[i]->WeightSize(); - OutputType err = errorTmp.cols(startCol, startCol + cols - 1); - err.reshape(input.n_cols, err.n_elem / input.n_cols); - // TODO: what about layerGradients? - OutputType gradientAlias(gradient.colptr(startParam, 1, params, false, - true); + MatType err = errorTmp.cols(startCol, startCol + cols - 1); + err.reshape(err.n_elem / input.n_cols, input.n_cols); + MatType gradientAlias; + MakeAlias(gradientAlias, + (typename MatType::elem_type*) gradient.colptr(startParam), + 1, + params); this->network[i]->Gradient(input, err, gradientAlias); startCol += cols; @@ -211,16 +276,15 @@ void ConcatType::Gradient( } } -// TODO: adapt -template -void ConcatType::Gradient( - const InputType& input, - const OutputType& error, - OutputType& gradient, +template +void ConcatType::Gradient( + const MatType& input, + const MatType& error, + MatType& gradient, const size_t index) { // Just like the forward pass, we can treat our inputs as a cube, but here we - // have to distribute the correct parts of `gy` to the layers. + // have to distribute the correct parts of `error` to the layers. size_t slices = (axis == 0) ? input.n_cols : std::accumulate(this->outputDimensions.begin(), @@ -229,8 +293,12 @@ void ConcatType::Gradient( std::accumulate(this->outputDimensions.begin() + axis + 1, this->outputDimensions.end(), 0); - arma::Cube errorTmp(error.memptr(), rows, - this->outputDimensions[axis], slices, false, true); + arma::Cube errorTmp; + MakeAlias(errorTmp, + (typename MatType::elem_type*) error.memptr(), + rows, + this->outputDimensions[axis], + slices); size_t startCol = 0; size_t startParam = 0; @@ -243,19 +311,22 @@ void ConcatType::Gradient( const size_t cols = this->network[index]->OutputDimensions()[axis]; const size_t params = this->network[index]->WeightSize(); - // TODO: no copy! - OutputType err = errorTmp.cols(startCol, startCol + cols - 1); - err.reshape(input.n_cols, err.n_elem / input.n_cols); - OutputType gradientAlias(gradient.memptr(), 1, params, false, true); + MatType err = errorTmp.cols(startCol, startCol + cols - 1); + err.reshape(err.n_elem / input.n_cols, input.n_cols); + MatType gradientAlias; + MakeAlias(gradientAlias, + (typename MatType::elem_type*) gradient.colptr(startParam), + 1, + params); this->network[index]->Gradient(input, err, gradientAlias); } -template +template template -void ConcatType::serialize( +void ConcatType::serialize( Archive& ar, const uint32_t /* version */) { - ar(cereal::base_class>(this)); + ar(cereal::base_class>(this)); ar(CEREAL_NVP(axis)); ar(CEREAL_NVP(useAxis)); diff --git a/src/mlpack/methods/ann/layer/layer_types.hpp b/src/mlpack/methods/ann/layer/layer_types.hpp index fb179b6457..ec1848f486 100644 --- a/src/mlpack/methods/ann/layer/layer_types.hpp +++ b/src/mlpack/methods/ann/layer/layer_types.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/src/mlpack/methods/ann/layer/serialization.hpp b/src/mlpack/methods/ann/layer/serialization.hpp index 5e13d2a9b4..5b0306cfa3 100644 --- a/src/mlpack/methods/ann/layer/serialization.hpp +++ b/src/mlpack/methods/ann/layer/serialization.hpp @@ -29,6 +29,7 @@ CEREAL_REGISTER_TYPE(mlpack::ann::ElishType<__VA_ARGS__>); \ CEREAL_REGISTER_TYPE(mlpack::ann::GaussianType<__VA_ARGS__>); \ /* (end of base_layer.hpp) */ \ + CEREAL_REGISTER_TYPE(mlpack::ann::ConcatType<__VA_ARGS__>); \ CEREAL_REGISTER_TYPE(mlpack::ann::ConcatenateType<__VA_ARGS__>); \ CEREAL_REGISTER_TYPE(mlpack::ann::ConvolutionType< \ mlpack::ann::NaiveConvolution, \