Turns out the MaxPooling adaptation I did was wrong---this seems more correct.

This commit is contained in:
Ryan Curtin
2021-11-14 22:30:39 -05:00
parent a1fd4829a1
commit c19e39a152
2 changed files with 105 additions and 85 deletions
+82 -41
View File
@@ -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<typename MatType>
size_t Pooling(const MatType& input)
template<typename InputType>
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<typename InputType>
std::tuple<size_t, typename InputType::elem_type> 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<size_t, typename InputType::elem_type>(index, maxVal);
}
};
@@ -166,46 +178,87 @@ class MaxPoolingType : public Layer<InputType, OutputType>
* @param output The pooled result.
* @param poolingIndices The pooled indices.
*/
void PoolingOperation(const InputType& input,
OutputType& output,
OutputType& poolingIndices)
void PoolingOperation(
const arma::Cube<typename InputType::elem_type>& input,
arma::Cube<typename OutputType::elem_type>& output,
arma::Cube<size_t>& 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<size_t> subIndices = indices(arma::span(rowidx,
rowidx + kernelWidth - 1 - offset),
arma::span(colidx, colidx + kernelHeight - 1 - offset));
const std::tuple<size_t, typename InputType::elem_type> 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<typename InputType::elem_type>& input,
arma::Cube<typename OutputType::elem_type>& 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<typename InputType::elem_type>& error,
arma::Cube<typename OutputType::elem_type>& output,
const arma::Cube<size_t>& 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<InputType, OutputType>
//! 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<size_t> indices;
//! Locally-stored indices column parameter.
arma::Col<size_t> indicesCol;
//! Locally-stored pooling indicies.
std::vector<arma::Cube<typename InputType::elem_type>> poolingIndices;
arma::Cube<size_t> poolingIndices;
}; // class MaxPoolingType
// Standard MaxPooling layer.
@@ -38,9 +38,7 @@ MaxPoolingType<InputType, OutputType>::MaxPoolingType(
strideHeight(strideHeight),
floor(floor),
channels(0),
reset(false),
offset(0),
batchSize(0)
offset(0)
{
// Nothing to do here.
}
@@ -49,67 +47,45 @@ template<typename InputType, typename OutputType>
void MaxPoolingType<InputType, OutputType>::Forward(
const InputType& input, OutputType& output)
{
batchSize = input.n_cols;
arma::Cube<typename InputType::elem_type> inputTemp(
const_cast<InputType&>(input).memptr(), this->inputDimensions[0],
this->inputDimensions[1], batchSize * channels, false, false);
this->inputDimensions[1], input.n_cols * channels, false, false);
arma::Cube<typename OutputType::elem_type> 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<arma::Col<size_t> >(0, (elements - 1),
elements);
indices = arma::Mat<size_t>(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<typename InputType, typename OutputType>
void MaxPoolingType<InputType, OutputType>::Backward(
const InputType& /* input */, const OutputType& gy, OutputType& g)
const InputType& input, const OutputType& gy, OutputType& g)
{
arma::Cube<typename OutputType::elem_type> mappedError =
arma::Cube<typename OutputType::elem_type>(((OutputType&) gy).memptr(),
this->outputDimensions[0], this->outputDimensions[1],
channels * batchSize, false, false);
channels * input.n_cols, false, false);
arma::Cube<typename OutputType::elem_type> 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<typename InputType, typename OutputType>
@@ -117,6 +93,7 @@ template<typename Archive>
void MaxPoolingType<InputType, OutputType>::serialize(
Archive& ar,
const uint32_t /* version */)
{
ar(cereal::base_class<Layer<InputType, OutputType>>(this));
@@ -124,13 +101,15 @@ void MaxPoolingType<InputType, OutputType>::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