blob: 8ff2eb457a3d9fbc57833ab120f1ea02fd8c3bec [file] [log] [blame]
//
// Copyright © 2017 Arm Ltd. All rights reserved.
// SPDX-License-Identifier: MIT
//
#include "Pooling2d.hpp"
#include <armnn/Exceptions.hpp>
#include <armnn/Types.hpp>
#include <armnnUtils/DataLayoutIndexed.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <limits>
#include <algorithm>
#include <functional>
namespace
{
using PoolingAlgorithm = armnn::PoolingAlgorithm;
float DefaultInitializer(PoolingAlgorithm algorithm)
{
switch (algorithm)
{
case PoolingAlgorithm::Max:
{
return std::numeric_limits<float>::lowest();
}
case PoolingAlgorithm::Average:
case PoolingAlgorithm::L2:
{
return 0.0f;
}
default:
{
throw armnn::InvalidArgumentException("Unsupported pooling algorithm");
}
}
}
using Accumulator = std::function<void(float & accu, float value)>;
Accumulator GetAccumulator(PoolingAlgorithm algorithm)
{
switch (algorithm)
{
case PoolingAlgorithm::Max:
{
return [](float & accu, float value) {
if (value > accu) {
accu = value;
}
};
}
case PoolingAlgorithm::Average:
{
return [](float & accu, float value) {
accu += value;
};
}
case PoolingAlgorithm::L2:
{
return [](float & accu, float value) {
accu += (value*value);
};
}
default:
{
throw armnn::InvalidArgumentException("Unsupported pooling algorithm");
}
}
}
using Executor = std::function<void(float & accumulated, float kernelSize)>;
Executor GetExecutor(PoolingAlgorithm algorithm)
{
switch (algorithm)
{
case PoolingAlgorithm::Max:
{
return [](float & /*accumulated*/, float /*kernelSize*/) {};
}
case PoolingAlgorithm::Average:
{
return [](float & accumulated, float kernelSize) {
accumulated /= kernelSize;
};
}
case PoolingAlgorithm::L2:
{
return [](float & accumulated, float kernelSize) {
accumulated = sqrtf(accumulated / kernelSize);
};
}
default:
{
throw armnn::InvalidArgumentException("Unsupported pooling algorithm");
}
}
}
bool OnPaddingOnly(int start, int end, int maxRange)
{
if (end <= 0 || start > maxRange)
{
return true;
}
else
{
return false;
}
}
bool ClampRange(int & start, int & end, int maxRange)
{
if (start < 0 || end > maxRange)
{
start = std::min(std::max(start, 0), maxRange);
end = std::min(std::max(end, 0), maxRange);
return true;
}
else
{
return false;
}
}
}
using namespace armnnUtils;
namespace armnn
{
void Pooling2d(Decoder<float>& rInputDecoder,
Encoder<float>& rOutputEncoder,
const TensorInfo& inputInfo,
const TensorInfo& outputInfo,
const Pooling2dDescriptor& params)
{
const DataLayoutIndexed dataLayout(params.m_DataLayout);
auto channelsIndex = dataLayout.GetChannelsIndex();
auto heightIndex = dataLayout.GetHeightIndex();
auto widthIndex = dataLayout.GetWidthIndex();
const int batchSize = boost::numeric_cast<int>(outputInfo.GetShape()[0]);
const int channels = boost::numeric_cast<int>(outputInfo.GetShape()[channelsIndex]);
const int heightOutput = boost::numeric_cast<int>(outputInfo.GetShape()[heightIndex]);
const int widthOutput = boost::numeric_cast<int>(outputInfo.GetShape()[widthIndex]);
const int heightInput = boost::numeric_cast<int>(inputInfo.GetShape()[heightIndex]);
const int widthInput = boost::numeric_cast<int>(inputInfo.GetShape()[widthIndex]);
const int padLeft = boost::numeric_cast<int>(params.m_PadLeft);
const int padRight = boost::numeric_cast<int>(params.m_PadRight);
const int padTop = boost::numeric_cast<int>(params.m_PadTop);
const int padBottom = boost::numeric_cast<int>(params.m_PadBottom);
const int strideX = boost::numeric_cast<int>(params.m_StrideX);
const int strideY = boost::numeric_cast<int>(params.m_StrideY);
const int poolHeight = boost::numeric_cast<int>(params.m_PoolHeight);
const int poolWidth = boost::numeric_cast<int>(params.m_PoolWidth);
float defaultInitializer = DefaultInitializer(params.m_PoolType);
Accumulator accumulate = GetAccumulator(params.m_PoolType);
Executor execute = GetExecutor(params.m_PoolType);
TensorShape outputShape = outputInfo.GetShape();
TensorShape inputShape = inputInfo.GetShape();
// Check supported padding methods outside the loop to simplify
// the inner loop.
if (params.m_PaddingMethod != PaddingMethod::Exclude &&
params.m_PaddingMethod != PaddingMethod::IgnoreValue)
{
throw armnn::InvalidArgumentException("Unsupported padding type");
}
for (int n = 0; n < batchSize; n++)
{
for (int c = 0; c < channels; c++)
{
for (int yOutput = 0; yOutput < heightOutput; yOutput++)
{
// Calculate values independent of the x axis
int hstart = (yOutput * strideY) - padTop;
int hend = hstart + poolHeight;
// Clamp the pooling region inside the valid input area (which includes the padding).
// This is necessary because the final pooling in a row may overlap beyond the padding.
hend = std::min(hend, heightInput + padBottom);
int height = hend - hstart;
bool hclamped = ClampRange(hstart, hend, heightInput);
for (int xOutput = 0; xOutput < widthOutput; xOutput++)
{
int wstart = (xOutput * strideX) - padLeft;
int wend = wstart + poolWidth;
// Clamp the pooling region inside the valid input area (which includes the padding).
// This is necessary because the final pooling in a row may overlap beyond the padding.
wend = std::min(wend, widthInput + padRight);
float result = defaultInitializer;
float poolAreaSize = boost::numeric_cast<float>(height * (wend - wstart));
// Special case: when the pooling kernel is over a padding region and the padding
// size is larger or equal to the kernel and the kernel only covers
// padding and no real values, then we initialize the result as zero
// by convention. This is because we need to choose a value here and
// all values we have are padding, which we ignore.
if (OnPaddingOnly(hstart, hend, heightInput) ||
OnPaddingOnly(wstart, wend, widthInput))
{
result = 0.0f;
unsigned int outputIndex = dataLayout.GetIndex(outputShape,
boost::numeric_cast<unsigned int>(n),
boost::numeric_cast<unsigned int>(c),
boost::numeric_cast<unsigned int>(yOutput),
boost::numeric_cast<unsigned int>(xOutput));
rOutputEncoder[outputIndex];
rOutputEncoder.Set(result);
continue;
}
bool clamped = hclamped |= ClampRange(wstart, wend, widthInput);
if (clamped && params.m_PaddingMethod == PaddingMethod::Exclude)
{
// When we exclude the padding, it means we calculate with a smaller
// kernel size, so I changed the divisor here.
poolAreaSize = boost::numeric_cast<float>((hend - hstart) * (wend - wstart));
}
for (auto yInput = hstart; yInput < hend; yInput++)
{
for (auto xInput = wstart; xInput < wend; xInput++)
{
unsigned int inputIndex = dataLayout.GetIndex(inputShape,
boost::numeric_cast<unsigned int>(n),
boost::numeric_cast<unsigned int>(c),
boost::numeric_cast<unsigned int>(yInput),
boost::numeric_cast<unsigned int>(xInput));
rInputDecoder[inputIndex];
float inval = rInputDecoder.Get();
accumulate(result, inval);
}
}
execute(result, poolAreaSize);
unsigned int outputIndex = dataLayout.GetIndex(outputShape,
boost::numeric_cast<unsigned int>(n),
boost::numeric_cast<unsigned int>(c),
boost::numeric_cast<unsigned int>(yOutput),
boost::numeric_cast<unsigned int>(xOutput));
rOutputEncoder[outputIndex];
rOutputEncoder.Set(result);
}
}
}
}
}
} //namespace armnn