From c19e39a152f36beea3b322c32327ec764c18897f Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Sun, 14 Nov 2021 22:30:39 -0500 Subject: [PATCH] Turns out the MaxPooling adaptation I did was wrong---this seems more correct. --- src/mlpack/methods/ann/layer/max_pooling.hpp | 123 ++++++++++++------ .../methods/ann/layer/max_pooling_impl.hpp | 67 ++++------ 2 files changed, 105 insertions(+), 85 deletions(-) diff --git a/src/mlpack/methods/ann/layer/max_pooling.hpp b/src/mlpack/methods/ann/layer/max_pooling.hpp index e5d516db6b..76fb7f14cd 100644 --- a/src/mlpack/methods/ann/layer/max_pooling.hpp +++ b/src/mlpack/methods/ann/layer/max_pooling.hpp @@ -28,12 +28,24 @@ class MaxPoolingRule /* * Return the maximum value within the receptive block. * - * @param input Input used to perform the pooling operation. + * @param input Input used to perform the pooling operation. Could be an + * Armadillo subview. */ - template - size_t Pooling(const MatType& input) + template + typename InputType::elem_type Pooling(const InputType& input) { - return arma::as_scalar(arma::find(input.max() == input, 1)); + return arma::max(arma::vectorise(input)); + } + + template + std::tuple PoolingWithIndex( + const InputType& input) + { + const typename InputType::elem_type maxVal = + arma::max(arma::vectorise(input)); + const size_t index = arma::as_scalar(arma::find(input == maxVal, 1)); + + return std::tuple(index, maxVal); } }; @@ -166,46 +178,87 @@ class MaxPoolingType : public Layer * @param output The pooled result. * @param poolingIndices The pooled indices. */ - void PoolingOperation(const InputType& input, - OutputType& output, - OutputType& poolingIndices) + void PoolingOperation( + const arma::Cube& input, + arma::Cube& output, + arma::Cube& poolingIndices) { - for (size_t j = 0, colidx = 0; j < output.n_cols; - ++j, colidx += strideHeight) + // Iterate over all slices individually. + for (size_t s = 0; s < input.n_slices; ++s) { - for (size_t i = 0, rowidx = 0; i < output.n_rows; - ++i, rowidx += strideWidth) + for (size_t j = 0, colidx = 0; j < output.n_cols; + ++j, colidx += strideHeight) { - InputType subInput = input( - arma::span(rowidx, rowidx + kernelWidth - 1 - offset), - arma::span(colidx, colidx + kernelHeight - 1 - offset)); - - const size_t idx = pooling.Pooling(subInput); - output(i, j) = subInput(idx); - - if (this->training) + for (size_t i = 0, rowidx = 0; i < output.n_rows; + ++i, rowidx += strideWidth) { - arma::Mat subIndices = indices(arma::span(rowidx, - rowidx + kernelWidth - 1 - offset), - arma::span(colidx, colidx + kernelHeight - 1 - offset)); + const std::tuple poolResult = + pooling.PoolingWithIndex(input.slice(s).submat( + rowidx, + colidx, + rowidx + kernelWidth - 1 - offset, + colidx + kernelHeight - 1 - offset)); - poolingIndices(i, j) = subIndices(idx); + // Now map the returned pooling index, which corresponds to the + // submatrix we gave, back to its position in the (linearized) input. + const size_t poolIndex = std::get<0>(poolResult); + const size_t poolingCol = poolIndex / (kernelWidth - offset); + const size_t poolingRow = poolIndex % (kernelWidth - offset); + const size_t unmappedPoolingIndex = (rowidx + poolingRow) + + input.n_rows * (colidx + poolingCol) + + input.n_rows * input.n_cols * s; + + poolingIndices(i, j, s) = unmappedPoolingIndex; + output(i, j, s) = std::get<1>(poolResult); } } } } /** - * Apply unpooling to the input and store the results. + * Apply pooling to all slices of the input and store the results, but not the + * indices used. + * + * @param input The input to apply the pooling rule to. + * @param output The pooled result. + */ + void PoolingOperation( + const arma::Cube& input, + arma::Cube& output) + { + // Iterate over all slices individually. + for (size_t s = 0; s < input.n_slices; ++s) + { + for (size_t j = 0, colidx = 0; j < output.n_cols; + ++j, colidx += strideHeight) + { + for (size_t i = 0, rowidx = 0; i < output.n_rows; + ++i, rowidx += strideWidth) + { + output(i, j, s) = pooling.Pooling(input.slice(s).submat( + rowidx, + colidx, + rowidx + kernelWidth - 1 - offset, + colidx + kernelHeight - 1 - offset)); + } + } + } + } + + /** + * Apply unpooling to all slices of the input and store the results. * * @param error The backward error. * @param output The pooled result. - * @param poolingIndices The pooled indices. + * @param poolingIndices The pooled indices (from `PoolingOperation()`). */ - void Unpooling(const InputType& error, - OutputType& output, - OutputType& poolingIndices) + void UnpoolingOperation( + const arma::Cube& error, + arma::Cube& output, + const arma::Cube& poolingIndices) { + output.zeros(); + for (size_t i = 0; i < poolingIndices.n_elem; ++i) { output(poolingIndices(i)) += error(i); @@ -230,26 +283,14 @@ class MaxPoolingType : public Layer //! Locally-stored number of channels. size_t channels; - //! Locally-stored reset parameter used to initialize the module once. - bool reset; - //! Locally-stored stored rounding offset. size_t offset; - //! Locally-stored number of input units. - size_t batchSize; - //! Locally-stored pooling strategy. MaxPoolingRule pooling; - //! Locally-stored indices matrix parameter. - arma::Mat indices; - - //! Locally-stored indices column parameter. - arma::Col indicesCol; - //! Locally-stored pooling indicies. - std::vector> poolingIndices; + arma::Cube poolingIndices; }; // class MaxPoolingType // Standard MaxPooling layer. diff --git a/src/mlpack/methods/ann/layer/max_pooling_impl.hpp b/src/mlpack/methods/ann/layer/max_pooling_impl.hpp index 8ec97af44d..41d5319ae1 100644 --- a/src/mlpack/methods/ann/layer/max_pooling_impl.hpp +++ b/src/mlpack/methods/ann/layer/max_pooling_impl.hpp @@ -38,9 +38,7 @@ MaxPoolingType::MaxPoolingType( strideHeight(strideHeight), floor(floor), channels(0), - reset(false), - offset(0), - batchSize(0) + offset(0) { // Nothing to do here. } @@ -49,67 +47,45 @@ template void MaxPoolingType::Forward( const InputType& input, OutputType& output) { - batchSize = input.n_cols; arma::Cube inputTemp( const_cast(input).memptr(), this->inputDimensions[0], - this->inputDimensions[1], batchSize * channels, false, false); + this->inputDimensions[1], input.n_cols * channels, false, false); arma::Cube outputTemp(output.memptr(), this->outputDimensions[0], this->outputDimensions[1], - batchSize * channels, false, true); + input.n_cols * channels, false, true); if (this->training) { - poolingIndices.push_back(outputTemp); + // If we are training, we'll do a backwards pass, so we need to ensure that + // we know what indices we used. + poolingIndices.set_size(this->outputDimensions[0], + this->outputDimensions[1], input.n_cols * channels); + + PoolingOperation(inputTemp, outputTemp, poolingIndices); } - - if (!reset) + else { - const size_t elements = this->inputDimensions[0] * this->inputDimensions[1]; - indicesCol = arma::linspace >(0, (elements - 1), - elements); - - indices = arma::Mat(indicesCol.memptr(), this->inputDimensions[0], - this->inputDimensions[1]); - - reset = true; - } - - for (size_t s = 0; s < inputTemp.n_slices; s++) - { - if (this->training) - { - PoolingOperation(inputTemp.slice(s), outputTemp.slice(s), - poolingIndices.back().slice(s)); - } - else - { - PoolingOperation(inputTemp.slice(s), outputTemp.slice(s), - inputTemp.slice(s)); - } + PoolingOperation(inputTemp, outputTemp); } } template void MaxPoolingType::Backward( - const InputType& /* input */, const OutputType& gy, OutputType& g) + const InputType& input, const OutputType& gy, OutputType& g) { arma::Cube mappedError = arma::Cube(((OutputType&) gy).memptr(), this->outputDimensions[0], this->outputDimensions[1], - channels * batchSize, false, false); + channels * input.n_cols, false, false); arma::Cube gTemp(g.memptr(), - this->inputDimensions[0], this->inputDimensions[1], channels * batchSize, - false, true); + this->inputDimensions[0], this->inputDimensions[1], + channels * input.n_cols, false, true); - for (size_t s = 0; s < mappedError.n_slices; s++) - { - Unpooling(mappedError.slice(s), gTemp.slice(s), - poolingIndices.back().slice(s)); - } - - poolingIndices.pop_back(); + // There's no version of UnpoolingOperation without pooling indices, because + // if we call `Backward()`, we know for sure we are training. + UnpoolingOperation(mappedError, gTemp, poolingIndices); } template @@ -117,6 +93,7 @@ template void MaxPoolingType::serialize( Archive& ar, const uint32_t /* version */) + { ar(cereal::base_class>(this)); @@ -124,13 +101,15 @@ void MaxPoolingType::serialize( ar(CEREAL_NVP(kernelHeight)); ar(CEREAL_NVP(strideWidth)); ar(CEREAL_NVP(strideHeight)); - ar(CEREAL_NVP(batchSize)); ar(CEREAL_NVP(channels)); ar(CEREAL_NVP(floor)); ar(CEREAL_NVP(offset)); if (Archive::is_loading::value) - reset = false; + { + // Clear any memory used by `poolingIndices`. + poolingIndices.clear(); + } } } // namespace ann