Unverified Commit b070ef8d authored by Adam Procter's avatar Adam Procter Committed by GitHub

Three changes to pooling (#437)

1. AvgPoolBackprop type checking
2. Padding for max-pool op and kernel
3. Max pool backprop kernel, and integrating everything with CPU backend/serializer
3a. Add optional backpointer from the max pool backprop op to the forward prop op
parent dfb88350
......@@ -29,7 +29,7 @@ op::AvgPool::AvgPool(const std::shared_ptr<Node>& arg,
, m_padding_below(padding_below)
, m_padding_above(padding_above)
{
auto& arg_shape = get_inputs().at(0).get_shape();
auto& arg_shape = get_input_shape(0);
//
// Make sure arg: NCDi for some Di of rank>0, N != 0, C != 0.
......@@ -56,7 +56,7 @@ op::AvgPool::AvgPool(const std::shared_ptr<Node>& arg,
size_t spatial_dimension_count = arg_shape.size() - 2;
//
// Make sure window shape, window movement strides, and have same rank as Di.
// Make sure window shape, window movement strides, and padding have same rank as Di.
//
if (window_shape.size() != spatial_dimension_count)
{
......@@ -199,30 +199,165 @@ op::AvgPool::AvgPool(const std::shared_ptr<Node>& arg, const Shape& window_shape
{
}
op::AvgPoolBprop::AvgPoolBprop(const std::shared_ptr<Node>& arg,
const std::shared_ptr<Node>& delta,
const Shape& window_shape,
const Strides& window_movement_strides,
const Shape& padding_below,
const Shape& padding_above)
: RequiresTensorViewArgs("AvgPoolBprop", {arg, delta})
op::AvgPoolBackprop::AvgPoolBackprop(const Shape& forward_arg_shape,
const std::shared_ptr<Node>& delta,
const Shape& window_shape,
const Strides& window_movement_strides,
const Shape& padding_below,
const Shape& padding_above)
: RequiresTensorViewArgs("AvgPoolBackprop", {delta})
, m_forward_arg_shape(forward_arg_shape)
, m_window_shape(window_shape)
, m_window_movement_strides(window_movement_strides)
, m_padding_below(padding_below)
, m_padding_above(padding_above)
{
set_value_type_checked(get_input_element_type(0), arg->get_shape());
// --
// TODO: de-duplicate this code from AvgPool::AvgPool.
// --
auto& delta_shape = get_input_shape(0);
//
// Make sure arg: NCDi for some Di of rank>0, N != 0, C != 0.
//
if (forward_arg_shape.size() < 3)
{
throw ngraph_error(
"Average-pool backprop: data batch shape must have rank of at least 3 (one batch axis, "
"one channel axis, at least one spatial dimension).");
}
size_t batch_size = forward_arg_shape[0];
if (batch_size == 0)
{
throw ngraph_error("Average-pool backprop: data batch size is zero.");
}
size_t channel_count = forward_arg_shape[1];
if (channel_count == 0)
{
throw ngraph_error("Average-pool backprop: requires at least one feature channel.");
}
size_t spatial_dimension_count = forward_arg_shape.size() - 2;
//
// Make sure window shape, window movement strides, and padding have same rank as Di.
//
if (window_shape.size() != spatial_dimension_count)
{
throw ngraph_error(
"Average-pool backprop: window shape rank does not match number of spatial "
"dimensions.");
}
if (window_movement_strides.size() != spatial_dimension_count)
{
throw ngraph_error(
"Average-pool backprop: window movement stride rank does not match number of spatial "
"dimensions.");
}
if (padding_below.size() != spatial_dimension_count)
{
throw ngraph_error(
"Average-pool backprop: below-padding rank does not match number of spatial "
"dimensions.");
}
if (padding_above.size() != spatial_dimension_count)
{
throw ngraph_error(
"Average-pool backprop: above-padding rank does not match number of spatial "
"dimensions.");
}
//
// Extract input item shape Di and make sure all dimensions are larger than 0.
//
Shape input_item_virtual_shape;
for (size_t i = 0; i < spatial_dimension_count; i++)
{
size_t dim_size = forward_arg_shape[1 + 1 + i];
size_t virtual_dim_size = padding_below[i] + dim_size + padding_above[i];
input_item_virtual_shape.push_back(virtual_dim_size);
if (virtual_dim_size == 0)
{
throw ngraph_error(
"Average-pool backprop: data batch spatial dimension is zero even after padding.");
}
}
//
// Make sure window shape dimensions are all larger than 0.
//
for (size_t i = 0; i < spatial_dimension_count; i++)
{
if (window_shape[i] == 0)
{
throw ngraph_error("Average-pool backprop: window shape has a zero-length axis.");
}
}
//
// Make the max pooling window fits within the spatial dimensions.
//
for (size_t i = 0; i < spatial_dimension_count; i++)
{
if (window_shape[i] > input_item_virtual_shape[i])
{
throw ngraph_error(
"Average-pool backprop: window shape is larger than the spatial dimensions even "
"after "
"padding.");
}
}
//
// Compute output item shape Do, checking at the same time that all window movement strides are larger than 0.
//
Shape output_item_shape;
for (size_t i = 0; i < spatial_dimension_count; i++)
{
if (window_movement_strides[i] == 0)
{
throw ngraph_error("Average-pool backprop: window axis movement stride is zero.");
}
output_item_shape.push_back(ceil_div(input_item_virtual_shape[i] - window_shape[i] + 1,
window_movement_strides[i]));
}
//
// Construct result shape: NCDo.
//
Shape forward_result_shape(1 + 1 + spatial_dimension_count);
forward_result_shape[0] = batch_size;
forward_result_shape[1] = channel_count;
std::copy(output_item_shape.begin(), output_item_shape.end(), forward_result_shape.begin() + 2);
if (forward_result_shape != delta_shape)
{
throw ngraph_error(
"Average-pool backprop: forward result shape does not match delta shape.");
}
set_value_type_checked(get_input_element_type(0), forward_arg_shape);
}
void op::AvgPool::generate_adjoints(autodiff::Adjoints& adjoints,
const std::shared_ptr<Node>& delta)
{
auto operand = get_input_op(0);
auto bprop = std::make_shared<op::AvgPoolBprop>(operand,
delta,
m_window_shape,
m_window_movement_strides,
m_padding_below,
m_padding_above);
adjoints.add_delta(operand, bprop);
auto& operand_shape = get_input_shape(0);
auto backprop = std::make_shared<op::AvgPoolBackprop>(operand_shape,
delta,
m_window_shape,
m_window_movement_strides,
m_padding_below,
m_padding_above);
adjoints.add_delta(operand, backprop);
}
......@@ -123,36 +123,38 @@ namespace ngraph
Shape m_padding_above;
};
class AvgPoolBprop : public RequiresTensorViewArgs
class AvgPoolBackprop : public RequiresTensorViewArgs
{
public:
AvgPoolBprop(const std::shared_ptr<Node>& arg,
const std::shared_ptr<Node>& delta,
const Shape& window_shape,
const Strides& window_movement_strides,
const Shape& padding_below,
const Shape& padding_above);
AvgPoolBackprop(const Shape& forward_arg_shape,
const std::shared_ptr<Node>& delta,
const Shape& window_shape,
const Strides& window_movement_strides,
const Shape& padding_below,
const Shape& padding_above);
virtual std::shared_ptr<Node> copy_with_new_args(
const std::vector<std::shared_ptr<Node>>& new_args) const override
{
if (new_args.size() != 2)
if (new_args.size() != 1)
throw ngraph_error("Incorrect number of new arguments");
AvgPoolBprop* avpn = new AvgPoolBprop(new_args.at(0),
new_args.at(1),
m_window_shape,
m_window_movement_strides,
m_padding_below,
m_padding_above);
return std::shared_ptr<op::AvgPoolBprop>(avpn);
AvgPoolBackprop* avpn = new AvgPoolBackprop(m_forward_arg_shape,
new_args.at(0),
m_window_shape,
m_window_movement_strides,
m_padding_below,
m_padding_above);
return std::shared_ptr<op::AvgPoolBackprop>(avpn);
}
const Shape& get_forward_arg_shape() const { return m_forward_arg_shape; }
const Shape& get_window_shape() const { return m_window_shape; }
const Strides& get_window_movement_strides() const { return m_window_movement_strides; }
const Shape& get_padding_below() const { return m_padding_below; }
const Shape& get_padding_above() const { return m_padding_above; }
protected:
Shape m_forward_arg_shape;
Shape m_window_shape;
Strides m_window_movement_strides;
Shape m_padding_below;
......
This diff is collapsed.
......@@ -20,7 +20,9 @@ namespace ngraph
{
namespace op
{
/// \brief Batched max pooling operation, with optional window stride.
/// \brief Batched max pooling operation, with optional padding and window stride.
///
/// (TODO: add an account of the optional padding to this comment.)
///
/// Max pooling takes as its input a data batch tensor of shape \f$(N,C,d_1,\dots,d_n)\f$ where \f$n > 0\f$, every \f$d_i > 0\f$, and where \f$N\f$ is
/// the batch size, and \f$C > 0\f$ is the number of channels (sometimes called features). The dimensions \f$(d_1,\dots,d_n)\f$ correspond to the shape of
......@@ -45,11 +47,24 @@ namespace ngraph
/// \param arg The node producing the input data batch tensor.
/// \param window_shape The window shape.
/// \param window_movement_strides The window movement strides.
/// \param padding_below The below-padding shape.
/// \param padding_above The above-padding shape.
MaxPool(const std::shared_ptr<Node>& arg,
const Shape& window_shape,
const Strides& window_movement_strides,
const Shape& padding_below,
const Shape& padding_above);
/// \brief Constructs a batched, unpadded max pooling operation (i.e., all padding shapes are set to 0).
///
/// \param arg The node producing the input data batch tensor.
/// \param window_shape The window shape.
/// \param window_movement_strides The window movement strides.
MaxPool(const std::shared_ptr<Node>& arg,
const Shape& window_shape,
const Strides& window_movement_strides);
/// \brief Constructs an unstrided batched convolution operation (i.e., all window movement strides are 1).
/// \brief Constructs an unstrided batched max pooling operation (i.e., all window movement strides are 1 and all padding shapes are set to 0).
///
/// \param arg The node producing the input data batch tensor.
/// \param window_shape The window shape.
......@@ -68,12 +83,61 @@ namespace ngraph
const Shape& get_window_shape() const { return m_window_shape; }
/// \return The window movement strides.
const Strides& get_window_movement_strides() const { return m_window_movement_strides; }
/// \return The below-padding shape.
const Shape& get_padding_below() const { return m_padding_below; }
/// \return The above-padding shape.
const Shape& get_padding_above() const { return m_padding_above; }
protected:
virtual void generate_adjoints(autodiff::Adjoints& adjoints,
const std::shared_ptr<Node>& delta) override;
Shape m_window_shape;
Strides m_window_movement_strides;
Shape m_padding_below;
Shape m_padding_above;
};
class MaxPoolBackprop : public RequiresTensorViewArgs
{
public:
MaxPoolBackprop(const std::shared_ptr<Node>& arg_forward,
const std::shared_ptr<Node>& delta,
const Shape& window_shape,
const Strides& window_movement_strides,
const Shape& padding_below,
const Shape& padding_above,
const std::shared_ptr<op::MaxPool>& forward_op = nullptr);
virtual std::shared_ptr<Node> copy_with_new_args(
const std::vector<std::shared_ptr<Node>>& new_args) const override
{
if (new_args.size() != 2)
throw ngraph_error("Incorrect number of new arguments");
MaxPoolBackprop* mpbp = new MaxPoolBackprop(new_args.at(0),
new_args.at(1),
m_window_shape,
m_window_movement_strides,
m_padding_below,
m_padding_above);
return std::shared_ptr<op::MaxPoolBackprop>(mpbp);
}
const Shape& get_window_shape() const { return m_window_shape; }
const Strides& get_window_movement_strides() const { return m_window_movement_strides; }
const Shape& get_padding_below() const { return m_padding_below; }
const Shape& get_padding_above() const { return m_padding_above; }
/// \return A pointer to the corresponding `MaxPool` forward prop op. This may be
/// `nullptr` if no such pointer was provided at construction time, or if the
/// forward op has been freed due to graph rewriting.
std::shared_ptr<op::MaxPool> get_forward_op() const;
protected:
Shape m_window_shape;
Strides m_window_movement_strides;
Shape m_padding_below;
Shape m_padding_above;
std::weak_ptr<op::MaxPool> m_forward_op;
};
}
}
......@@ -2120,8 +2120,10 @@ void runtime::cpu::CPU_Emitter::EmitMaxPool(codegen::CodeWriter& writer,
writer << "auto max_pooling = pooling_forward({"
<< "{prop_kind::forward_inference, algorithm::pooling_max, "
<< "input_data_desc, result_desc, {" << join(max_pool->get_window_movement_strides())
<< "}, {" << join(max_pool->get_window_shape()) << "}, {0, 0}, "
<< "{0, 0}, padding_kind::zero}, cpu_engine}, "
<< "}, {" << join(max_pool->get_window_shape()) << "}, {"
<< join(max_pool->get_padding_below()) << "}, "
<< "{" << join(max_pool->get_padding_above())
<< "}, padding_kind::zero}, cpu_engine}, "
<< "input_data, result);\n";
writer << "auto s = stream(stream::kind::eager);\n"
......@@ -2136,7 +2138,9 @@ void runtime::cpu::CPU_Emitter::EmitMaxPool(codegen::CodeWriter& writer,
writer << " {" << join(arg_shape) << "},\n";
writer << " {" << join(result_shape) << "},\n";
writer << " {" << join(max_pool->get_window_shape()) << "},\n";
writer << " {" << join(max_pool->get_window_movement_strides()) << "});\n";
writer << " {" << join(max_pool->get_window_movement_strides()) << "},\n";
writer << " {" << join(max_pool->get_padding_below()) << "},\n";
writer << " {" << join(max_pool->get_padding_above()) << "});\n";
}
}
......@@ -2341,22 +2345,22 @@ void runtime::cpu::CPU_Emitter::EmitPad(codegen::CodeWriter& writer,
writer << " {" << join(pad->get_padding_interior()) << "});\n";
}
void runtime::cpu::CPU_Emitter::EmitAvgPoolBprop(
void runtime::cpu::CPU_Emitter::EmitAvgPoolBackprop(
codegen::CodeWriter& writer,
const ngraph::Node* n,
const vector<runtime::cpu::TensorViewWrapper>& args,
const vector<runtime::cpu::TensorViewWrapper>& out)
{
auto apb = static_cast<const op::AvgPoolBprop*>(n);
auto apb = static_cast<const op::AvgPoolBackprop*>(n);
auto arg_shape = args[0].get_shape();
auto delta_shape = args[1].get_shape();
auto delta_shape = args[0].get_shape();
auto out_shape = out[0].get_shape();
writer << "kernel::avg_pool_bprop<" << out[0].get_type() << ">(" << args[0].get_name() << ",\n";
writer << " " << args[1].get_name() << ",\n";
writer << "kernel::avg_pool_backprop<" << out[0].get_type() << ">(" << args[0].get_name()
<< ",\n";
writer << " " << out[0].get_name() << ",\n";
writer << " {" << join(arg_shape) << "},\n";
writer << " {" << join(delta_shape) << "},\n";
writer << " {" << join(out_shape) << "},\n";
writer << " {" << join(apb->get_window_shape()) << "},\n";
writer << " {" << join(apb->get_window_movement_strides()) << "},\n";
writer << " {" << join(apb->get_padding_below()) << "},\n";
......@@ -2364,6 +2368,30 @@ void runtime::cpu::CPU_Emitter::EmitAvgPoolBprop(
writer << " true);\n";
}
void runtime::cpu::CPU_Emitter::EmitMaxPoolBackprop(
codegen::CodeWriter& writer,
const ngraph::Node* n,
const vector<runtime::cpu::TensorViewWrapper>& args,
const vector<runtime::cpu::TensorViewWrapper>& out)
{
auto mpb = static_cast<const op::MaxPoolBackprop*>(n);
auto delta_shape = args[1].get_shape();
auto out_shape = out[0].get_shape();
writer << "kernel::max_pool_backprop<" << out[0].get_type() << ">(" << args[0].get_name()
<< ",\n";
writer << " " << args[1].get_name() << ",\n";
writer << " " << out[0].get_name() << ",\n";
writer << " {" << join(delta_shape) << "},\n";
writer << " {" << join(out_shape) << "},\n";
writer << " {" << join(mpb->get_window_shape()) << "},\n";
writer << " {" << join(mpb->get_window_movement_strides()) << "},\n";
writer << " {" << join(mpb->get_padding_below()) << "},\n";
writer << " {" << join(mpb->get_padding_above()) << "},\n";
writer << " true);\n";
}
//------------------------------------------------------------------------------------------------
// Utility methods
//------------------------------------------------------------------------------------------------
......
......@@ -94,8 +94,9 @@ namespace ngraph
static void EMITTER_DECL(EmitReduceWindow);
static void EMITTER_DECL(EmitSelectAndScatter);
static void EMITTER_DECL(EmitAvgPool);
static void EMITTER_DECL(EmitAvgPoolBprop);
static void EMITTER_DECL(EmitAvgPoolBackprop);
static void EMITTER_DECL(EmitPad);
static void EMITTER_DECL(EmitMaxPoolBackprop);
static void EmitMKLDNNPreamble(codegen::CodeWriter& writer);
......
......@@ -200,8 +200,9 @@ static const runtime::cpu::OpMap dispatcher{
{TI(ngraph::op::ReduceWindow), &runtime::cpu::CPU_Emitter::EmitReduceWindow},
{TI(ngraph::op::SelectAndScatter), &runtime::cpu::CPU_Emitter::EmitSelectAndScatter},
{TI(ngraph::op::AvgPool), &runtime::cpu::CPU_Emitter::EmitAvgPool},
{TI(ngraph::op::AvgPoolBprop), &runtime::cpu::CPU_Emitter::EmitAvgPoolBprop},
{TI(ngraph::op::AvgPoolBackprop), &runtime::cpu::CPU_Emitter::EmitAvgPoolBackprop},
{TI(ngraph::op::Pad), &runtime::cpu::CPU_Emitter::EmitPad},
{TI(ngraph::op::MaxPoolBackprop), &runtime::cpu::CPU_Emitter::EmitMaxPoolBackprop},
};
runtime::cpu::CPU_ExternalFunction::CPU_ExternalFunction(
......
......@@ -258,20 +258,17 @@ private:
avg_pool->get_padding_below(),
avg_pool->get_padding_above());
}
else if (node_op == "AvgPoolBprop")
{
ngraph::op::AvgPoolBprop* apb = dynamic_cast<ngraph::op::AvgPoolBprop*>(&node);
kernel::avg_pool_bprop<T>(
reinterpret_cast<T*>(args[0]->get_data_ptr()),
reinterpret_cast<T*>(args[1]->get_data_ptr()),
reinterpret_cast<T*>(out[0]->get_data_ptr()),
args[0]->get_shape(),
args[1]->get_shape(), /*delta shape*/
apb->get_window_shape(),
apb->get_window_movement_strides(),
apb->get_padding_below(),
apb->get_padding_above(),
true /*divide by the number of physical elements in a window*/);
else if (node_op == "AvgPoolBackprop")
{
ngraph::op::AvgPoolBackprop* apb = dynamic_cast<ngraph::op::AvgPoolBackprop*>(&node);
kernel::avg_pool_backprop<T>(reinterpret_cast<T*>(args[0]->get_data_ptr()),
reinterpret_cast<T*>(out[0]->get_data_ptr()),
args[0]->get_shape(),
out[0]->get_shape(),
apb->get_window_shape(),
apb->get_window_movement_strides(),
apb->get_padding_below(),
apb->get_padding_above());
}
else if (node_op == "Broadcast")
{
......@@ -493,7 +490,24 @@ private:
args[0]->get_shape(),
out[0]->get_shape(),
max_pool->get_window_shape(),
max_pool->get_window_movement_strides());
max_pool->get_window_movement_strides(),
max_pool->get_padding_below(),
max_pool->get_padding_above());
}
else if (node_op == "MaxPoolBackprop")
{
ngraph::op::MaxPoolBackprop* max_pool_backprop =
dynamic_cast<ngraph::op::MaxPoolBackprop*>(&node);
kernel::max_pool_backprop<T>(reinterpret_cast<T*>(args[0]->get_data_ptr()),
reinterpret_cast<T*>(args[1]->get_data_ptr()),
reinterpret_cast<T*>(out[0]->get_data_ptr()),
args[1]->get_shape(),
out[0]->get_shape(),
max_pool_backprop->get_window_shape(),
max_pool_backprop->get_window_movement_strides(),
max_pool_backprop->get_padding_below(),
max_pool_backprop->get_padding_above());
}
else if (node_op == "Minimum")
{
......
......@@ -30,90 +30,89 @@ namespace ngraph
namespace kernel
{
template <typename T>
void avg_pool_bprop(T* arg,
T* delta,
T* out, //out is also arg_shape
const Shape& arg_shape,
const Shape& delta_shape,
const Shape& window_shape,
const Strides& window_movement_strides,
const Shape& padding_below,
const Shape& padding_above,
bool count_only_physical)
void avg_pool_backprop(T* delta,
T* out,
const Shape& delta_shape,
const Shape& out_shape,
const Shape& window_shape,
const Strides& window_movement_strides,
const Shape& padding_below,
const Shape& padding_above)
{
memset(out, 0, sizeof(T) * shape_size(arg_shape));
size_t j = 0; //for iterating over delta (ep) elements
size_t num_elements_in_window = shape_size(window_shape);
CoordinateTransform output_transform(delta_shape);
CoordinateTransform out_transform(out_shape);
for (const Coordinate& out_coord : output_transform)
for (const Coordinate& out_coord : out_transform)
{
size_t img_index = out_coord[0];
size_t channel = out_coord[1];
out[out_transform.index(out_coord)] = 0;
}
size_t n_image_dimensions = arg_shape.size() - 2;
Coordinate input_batch_transform_start(2 + n_image_dimensions);
Coordinate input_batch_transform_end(2 + n_image_dimensions);
Strides input_batch_transform_source_strides(2 + n_image_dimensions, 1);
AxisVector input_batch_transform_source_axis_order(2 + n_image_dimensions);
CoordinateDiff input_batch_transform_padding_below(2 + n_image_dimensions);
CoordinateDiff input_batch_transform_padding_above(2 + n_image_dimensions);
CoordinateTransform delta_transform(delta_shape);
input_batch_transform_start[0] = img_index;
input_batch_transform_end[0] = img_index + 1;
input_batch_transform_start[1] = channel;
input_batch_transform_end[1] = channel + 1;
input_batch_transform_padding_below[0] = 0;
input_batch_transform_padding_below[1] = 0;
input_batch_transform_padding_above[0] = 0;
input_batch_transform_padding_above[1] = 0;
for (const Coordinate& delta_coord : delta_transform)
{
size_t img_index = delta_coord[0];
size_t channel = delta_coord[1];
size_t n_image_dimensions = out_shape.size() - 2;
Coordinate source_window_transform_start(2 + n_image_dimensions);
Coordinate source_window_transform_end(2 + n_image_dimensions);
Strides source_window_transform_source_strides(2 + n_image_dimensions, 1);
AxisVector source_window_transform_source_axis_order(2 + n_image_dimensions);
CoordinateDiff source_window_transform_padding_below(2 + n_image_dimensions);
CoordinateDiff source_window_transform_padding_above(2 + n_image_dimensions);
source_window_transform_start[0] = img_index;
source_window_transform_end[0] = img_index + 1;
source_window_transform_start[1] = channel;
source_window_transform_end[1] = channel + 1;
source_window_transform_padding_below[0] = 0;
source_window_transform_padding_below[1] = 0;
source_window_transform_padding_above[0] = 0;
source_window_transform_padding_above[1] = 0;
for (size_t i = 2; i < n_image_dimensions + 2; i++)
{
size_t window_shape_this_dim = window_shape[i - 2];
size_t movement_stride = window_movement_strides[i - 2];
input_batch_transform_start[i] = movement_stride * out_coord[i];
input_batch_transform_end[i] =
input_batch_transform_start[i] + window_shape_this_dim;
input_batch_transform_padding_below[i] = padding_below[i - 2];
input_batch_transform_padding_above[i] = padding_above[i - 2];
source_window_transform_start[i] = movement_stride * delta_coord[i];
source_window_transform_end[i] =
source_window_transform_start[i] + window_shape_this_dim;
source_window_transform_padding_below[i] = padding_below[i - 2];
source_window_transform_padding_above[i] = padding_above[i - 2];
}
std::iota(begin(input_batch_transform_source_axis_order),
end(input_batch_transform_source_axis_order),
std::iota(begin(source_window_transform_source_axis_order),
end(source_window_transform_source_axis_order),
0);
CoordinateTransform input_batch_transform(
arg_shape,
input_batch_transform_start,
input_batch_transform_end,
input_batch_transform_source_strides,
input_batch_transform_source_axis_order,
input_batch_transform_padding_below,
input_batch_transform_padding_above);
CoordinateTransform source_window_transform(
out_shape,
source_window_transform_start,
source_window_transform_end,
source_window_transform_source_strides,
source_window_transform_source_axis_order,
source_window_transform_padding_below,
source_window_transform_padding_above);
size_t num_elements_in_window = 0;
if (count_only_physical)
for (const Coordinate& source_window_coord : source_window_transform)
{
num_elements_in_window = 0;
//Dumb! But should work for now
for (const Coordinate& input_batch_coord : input_batch_transform)
if (source_window_transform.has_source_coordinate(source_window_coord))
{
if (input_batch_transform.has_source_coordinate(input_batch_coord))
{
num_elements_in_window++;
}
num_elements_in_window++;
}
}
for (const Coordinate& input_batch_coord : input_batch_transform)
for (const Coordinate& source_window_coord : source_window_transform)
{
if (input_batch_transform.has_source_coordinate(input_batch_coord))
if (source_window_transform.has_source_coordinate(source_window_coord))
{
size_t index = input_batch_transform.index(input_batch_coord);
out[index] += delta[j] / num_elements_in_window;
size_t out_index = source_window_transform.index(source_window_coord);
out[out_index] +=
delta[delta_transform.index(delta_coord)] / num_elements_in_window;
}
}
j++; //move to the next ep
}
}
......
......@@ -14,7 +14,9 @@
#pragma once
#include <algorithm>
#include <cmath>
#include <numeric>
#include "ngraph/common.hpp"
#include "ngraph/coordinate_transform.hpp"
......@@ -25,13 +27,109 @@ namespace ngraph
{
namespace kernel
{
template <typename T>
void max_pool_backprop(T* arg_forward,
T* delta,
T* out,
const Shape& delta_shape,
const Shape& out_shape, // same as arg_forward_shape
const Shape& window_shape,
const Strides& window_movement_strides,
const Shape& padding_below,
const Shape& padding_above)
{
CoordinateTransform out_transform(out_shape);
for (const Coordinate& out_coord : out_transform)
{
out[out_transform.index(out_coord)] = 0;
}
CoordinateTransform delta_transform(delta_shape);
for (const Coordinate& delta_coord : delta_transform)
{
size_t img_index = delta_coord[0];
size_t channel = delta_coord[1];
size_t n_image_dimensions = out_shape.size() - 2;
Coordinate source_window_transform_start(2 + n_image_dimensions);
Coordinate source_window_transform_end(2 + n_image_dimensions);
Strides source_window_transform_source_strides(2 + n_image_dimensions, 1);
AxisVector source_window_transform_source_axis_order(2 + n_image_dimensions);
CoordinateDiff source_window_transform_padding_below(2 + n_image_dimensions);
CoordinateDiff source_window_transform_padding_above(2 + n_image_dimensions);
source_window_transform_start[0] = img_index;
source_window_transform_end[0] = img_index + 1;
source_window_transform_start[1] = channel;
source_window_transform_end[1] = channel + 1;
source_window_transform_padding_below[0] = 0;
source_window_transform_padding_below[1] = 0;
source_window_transform_padding_above[0] = 0;
source_window_transform_padding_above[1] = 0;
for (size_t i = 2; i < n_image_dimensions + 2; i++)
{
size_t window_shape_this_dim = window_shape[i - 2];
size_t movement_stride = window_movement_strides[i - 2];
source_window_transform_start[i] = movement_stride * delta_coord[i];
source_window_transform_end[i] =
source_window_transform_start[i] + window_shape_this_dim;
source_window_transform_padding_below[i] = padding_below[i - 2];
source_window_transform_padding_above[i] = padding_above[i - 2];
}
std::iota(begin(source_window_transform_source_axis_order),
end(source_window_transform_source_axis_order),
0);
CoordinateTransform source_window_transform(
out_shape,
source_window_transform_start,
source_window_transform_end,
source_window_transform_source_strides,
source_window_transform_source_axis_order,
source_window_transform_padding_below,
source_window_transform_padding_above);
Coordinate argmax_coord;
bool argmax_coord_valid = false;
T max_val = 0; // just initializing to keep compiler happy, this 0 is ignored
for (const Coordinate& source_window_coord : source_window_transform)
{
if (source_window_transform.has_source_coordinate(source_window_coord))
{
T candidate =
arg_forward[source_window_transform.index(source_window_coord)];
if (!argmax_coord_valid || candidate > max_val)
{
max_val = candidate;
argmax_coord = source_window_coord;
argmax_coord_valid = true;
}
}
}
if (argmax_coord_valid)
{
out[source_window_transform.index(argmax_coord)] +=
delta[delta_transform.index(delta_coord)];
}
}
}
template <typename T>
void max_pool(T* arg,
T* out,
const Shape& arg_shape,
const Shape& out_shape,
const Shape& window_shape,
const Strides& window_movement_strides)
const Strides& window_movement_strides,
const Shape& padding_below,
const Shape& padding_above)
{
// At the outermost level we will walk over every output coordinate O.
CoordinateTransform output_transform(out_shape);
......@@ -56,16 +154,26 @@ namespace ngraph
// (N+1,chan+1,s_1*i_1 + window_shape_1,...,s_n*i_n + window_shape_n)
//
// with unit stride.
//
// We iterate this over the *padded* data, so below we will need to check for coordinates that fall in the padding area.
size_t n_spatial_dimensions = arg_shape.size() - 2;
Coordinate input_batch_transform_start(2 + n_spatial_dimensions);
Coordinate input_batch_transform_end(2 + n_spatial_dimensions);
Strides input_batch_transform_source_strides(2 + n_spatial_dimensions, 1);
AxisVector input_batch_transform_source_axis_order(2 + n_spatial_dimensions);
CoordinateDiff input_batch_transform_padding_below(2 + n_spatial_dimensions);
CoordinateDiff input_batch_transform_padding_above(2 + n_spatial_dimensions);
input_batch_transform_start[0] = batch_index;
input_batch_transform_end[0] = batch_index + 1;
input_batch_transform_start[1] = channel;
input_batch_transform_end[1] = channel + 1;
input_batch_transform_padding_below[0] = 0;
input_batch_transform_padding_below[1] = 0;
input_batch_transform_padding_above[0] = 0;
input_batch_transform_padding_above[1] = 0;
for (size_t i = 2; i < n_spatial_dimensions + 2; i++)
{
......@@ -75,10 +183,23 @@ namespace ngraph
input_batch_transform_start[i] = movement_stride * out_coord[i];
input_batch_transform_end[i] =
input_batch_transform_start[i] + window_shape_this_dim;
input_batch_transform_padding_below[i] = padding_below[i - 2];
input_batch_transform_padding_above[i] = padding_above[i - 2];
}
for (size_t i = 0; i < arg_shape.size(); i++)
{
input_batch_transform_source_axis_order[i] = i;
}
CoordinateTransform input_batch_transform(
arg_shape, input_batch_transform_start, input_batch_transform_end);
arg_shape,
input_batch_transform_start,
input_batch_transform_end,
input_batch_transform_source_strides,
input_batch_transform_source_axis_order,
input_batch_transform_padding_below,
input_batch_transform_padding_above);
// As we go, we compute the maximum value:
//
......@@ -90,8 +211,11 @@ namespace ngraph
for (const Coordinate& input_batch_coord : input_batch_transform)
{
T x = arg[input_batch_transform.index(input_batch_coord)];
result = x > result ? x : result;
if (input_batch_transform.has_source_coordinate(input_batch_coord))
{
T x = arg[input_batch_transform.index(input_batch_coord)];
result = x > result ? x : result;
}
}
out[output_transform.index(out_coord)] = result;
......
......@@ -343,6 +343,21 @@ static shared_ptr<ngraph::Function>
node = make_shared<op::AvgPool>(
args[0], window_shape, window_movement_strides, padding_below, padding_above);
}
else if (node_op == "AvgPoolBackprop")
{
auto forward_arg_shape = node_js.at("forward_arg_shape").get<vector<size_t>>();
auto window_shape = node_js.at("window_shape").get<vector<size_t>>();
auto window_movement_strides =
node_js.at("window_movement_strides").get<vector<size_t>>();
auto padding_below = node_js.at("padding_below").get<vector<size_t>>();
auto padding_above = node_js.at("padding_above").get<vector<size_t>>();
node = make_shared<op::AvgPoolBackprop>(forward_arg_shape,
args[0],
window_shape,
window_movement_strides,
padding_below,
padding_above);
}
else if (node_op == "Broadcast")
{
auto shape = node_js.at("shape").get<vector<size_t>>();
......@@ -527,7 +542,45 @@ static shared_ptr<ngraph::Function>
auto window_shape = node_js.at("window_shape").get<vector<size_t>>();
auto window_movement_strides =
node_js.at("window_movement_strides").get<vector<size_t>>();
node = make_shared<op::MaxPool>(args[0], window_shape, window_movement_strides);
// For backwards compatibility, both (but not just one) of the padding_ fields may be
// omitted.
auto padding_below_maybe = node_js["padding_below"];
auto padding_above_maybe = node_js["padding_above"];
if (padding_below_maybe.empty() && !padding_above_maybe.empty())
{
throw runtime_error(
"MaxPool: padding_below is absent but padding_above is present");
}
else if (!padding_below_maybe.empty() && padding_above_maybe.empty())
{
throw runtime_error(
"MaxPool: padding_below is present but padding_above is absent");
}
else if (!padding_below_maybe.empty() && !padding_above_maybe.empty())
{
auto padding_below = padding_below_maybe.get<vector<size_t>>();
auto padding_above = padding_above_maybe.get<vector<size_t>>();
node = make_shared<op::MaxPool>(
args[0], window_shape, window_movement_strides, padding_below, padding_above);
}
else
{
node = make_shared<op::MaxPool>(args[0], window_shape, window_movement_strides);
}
}
else if (node_op == "MaxPoolBackprop")
{
auto window_shape = node_js.at("window_shape").get<vector<size_t>>();
auto window_movement_strides =
node_js.at("window_movement_strides").get<vector<size_t>>();
auto padding_below = node_js.at("padding_below").get<vector<size_t>>();
auto padding_above = node_js.at("padding_above").get<vector<size_t>>();
node = make_shared<op::MaxPoolBackprop>(args[0],
args[1],
window_shape,
window_movement_strides,
padding_below,
padding_above);
}
else if (node_op == "Maximum")
{
......@@ -754,6 +807,15 @@ static json write(const Node& n)
node["padding_below"] = tmp->get_padding_below();
node["padding_above"] = tmp->get_padding_above();
}
else if (node_op == "AvgPoolBackprop")
{
auto tmp = dynamic_cast<const op::AvgPoolBackprop*>(&n);
node["forward_arg_shape"] = tmp->get_forward_arg_shape();
node["window_shape"] = tmp->get_window_shape();
node["window_movement_strides"] = tmp->get_window_movement_strides();
node["padding_below"] = tmp->get_padding_below();
node["padding_above"] = tmp->get_padding_above();
}
else if (node_op == "Broadcast")
{
auto tmp = dynamic_cast<const op::Broadcast*>(&n);
......@@ -859,6 +921,16 @@ static json write(const Node& n)
auto tmp = dynamic_cast<const op::MaxPool*>(&n);
node["window_shape"] = tmp->get_window_shape();
node["window_movement_strides"] = tmp->get_window_movement_strides();
node["padding_below"] = tmp->get_padding_below();
node["padding_above"] = tmp->get_padding_above();
}
else if (node_op == "MaxPoolBackprop")
{
auto tmp = dynamic_cast<const op::MaxPoolBackprop*>(&n);
node["window_shape"] = tmp->get_window_shape();
node["window_movement_strides"] = tmp->get_window_movement_strides();
node["padding_below"] = tmp->get_padding_below();
node["padding_above"] = tmp->get_padding_above();
}
else if (node_op == "Maximum")
{
......
......@@ -4412,6 +4412,87 @@ TEST(${BACKEND_NAME}, max_pool_2d_2channel_2image)
read_vector<float>(result));
}
TEST(${BACKEND_NAME}, max_pool_2d_1channel_1image_padded)
{
auto shape_a = Shape{1, 1, 5, 5};
auto window_shape = Shape{2, 3};
auto window_movement_strides = Strides{1, 1};
auto padding_below = Shape{1, 0};
auto padding_above = Shape{1, 2};
auto A = make_shared<op::Parameter>(element::f32, shape_a);
auto shape_r = Shape{1, 1, 6, 5};
auto f = make_shared<Function>(
make_shared<op::MaxPool>(
A, window_shape, window_movement_strides, padding_below, padding_above),
op::Parameters{A});
auto manager = runtime::Manager::get("${BACKEND_NAME}");
auto external = manager->compile(f);
auto backend = manager->allocate_backend();
auto cf = backend->make_call_frame(external);
// Create some tensors for input/output
auto a = backend->make_primary_tensor_view(element::f32, shape_a);
copy_data(a,
test::NDArray<float, 4>({{{{0, 1, 0, 2, 1},
{0, 3, 2, 0, 0},
{2, 0, 0, 0, 1},
{2, 0, 1, 1, 2},
{0, 2, 1, 0, 0}}}})
.get_vector());
auto result = backend->make_primary_tensor_view(element::f32, shape_r);
cf->call({a}, {result});
EXPECT_EQ((test::NDArray<float, 4>({{{{1, 2, 2, 2, 1},
{3, 3, 2, 2, 1},
{3, 3, 2, 1, 1},
{2, 1, 2, 2, 2},
{2, 2, 2, 2, 2},
{2, 2, 1, 0, 0}}}})
.get_vector()),
read_vector<float>(result));
}
// Test to make sure that negative elements and padding are handled properly. Added this because
// mkldnn calls its padding "zero padding" but apparently that is not technically true (negative
// values still "win" versus out-of-bounds values), which is good.
TEST(${BACKEND_NAME}, max_pool_2d_1channel_1image_padded_negative_values)
{
auto shape_a = Shape{
1,
1,
1,
14}; // 1 image, 1 channel, 1 row, 14 columns (if it's 1D we don't get mkldnn as of this writing)
auto window_shape = Shape{1, 3};
auto window_movement_strides = Strides{1, 1};
auto padding_below = Shape{0, 1};
auto padding_above = Shape{0, 2};
auto A = make_shared<op::Parameter>(element::f32, shape_a);
auto shape_r = Shape{1, 1, 1, 15};
auto f = make_shared<Function>(
make_shared<op::MaxPool>(
A, window_shape, window_movement_strides, padding_below, padding_above),
op::Parameters{A});
auto manager = runtime::Manager::get("${BACKEND_NAME}");
auto external = manager->compile(f);
auto backend = manager->allocate_backend();
auto cf = backend->make_call_frame(external);
// Create some tensors for input/output
auto a = backend->make_primary_tensor_view(element::f32, shape_a);
copy_data(a,
test::NDArray<float, 4>{{{{-1, -2, -3, -3, -2, -1, -3, -2, -2, -2, -2, -3, -4, -5}}}}
.get_vector());
auto result = backend->make_primary_tensor_view(element::f32, shape_r);
cf->call({a}, {result});
EXPECT_EQ(
(test::NDArray<float, 4>({{{{-1, -1, -2, -2, -1, -1, -1, -2, -2, -2, -2, -2, -3, -4, -5}}}})
.get_vector()),
read_vector<float>(result));
}
TEST(${BACKEND_NAME}, max_pool_2d_1channel_1image_strided)
{
auto shape_a = Shape{1, 1, 8, 8};
......
......@@ -3326,7 +3326,7 @@ TEST(type_prop, max_pool_invalid_0d_input)
catch (const ngraph_error& error)
{
EXPECT_EQ(error.what(),
std::string("Max pool data batch input must have rank of at "
std::string("Max-pool data batch input must have rank of at "
"least 3 (one batch axis, one channel axis, at "
"least one spatial dimension)."));
}
......@@ -3351,7 +3351,7 @@ TEST(type_prop, max_pool_invalid_1d_input)
catch (const ngraph_error& error)
{
EXPECT_EQ(error.what(),
std::string("Max pool data batch input must have rank of at "
std::string("Max-pool data batch input must have rank of at "
"least 3 (one batch axis, one channel axis, at "
"least one spatial dimension)."));
}
......@@ -3376,7 +3376,7 @@ TEST(type_prop, max_pool_invalid_2d_input)
catch (const ngraph_error& error)
{
EXPECT_EQ(error.what(),
std::string("Max pool data batch input must have rank of at "
std::string("Max-pool data batch input must have rank of at "
"least 3 (one batch axis, one channel axis, at "
"least one spatial dimension)."));
}
......@@ -3400,7 +3400,7 @@ TEST(type_prop, max_pool_invalid_0_batch_size)
}
catch (const ngraph_error& error)
{
EXPECT_EQ(error.what(), std::string("Max pool data batch size is zero."));
EXPECT_EQ(error.what(), std::string("Max-pool data batch size is zero."));
}
catch (...)
{
......@@ -3422,7 +3422,7 @@ TEST(type_prop, max_pool_invalid_0_channels)
}
catch (const ngraph_error& error)
{
EXPECT_EQ(error.what(), std::string("Max pool requires at least one feature channel."));
EXPECT_EQ(error.what(), std::string("Max-pool requires at least one feature channel."));
}
catch (...)
{
......@@ -3446,7 +3446,7 @@ TEST(type_prop, max_pool_invalid_wrong_number_of_window_dimensions_too_many)
{
EXPECT_EQ(
error.what(),
std::string("Max pool window shape rank does not match number of spatial dimensions."));
std::string("Max-pool window shape rank does not match number of spatial dimensions."));
}
catch (...)
{
......@@ -3470,7 +3470,7 @@ TEST(type_prop, max_pool_invalid_wrong_number_of_window_dimensions_too_few)
{
EXPECT_EQ(
error.what(),
std::string("Max pool window shape rank does not match number of spatial dimensions."));
std::string("Max-pool window shape rank does not match number of spatial dimensions."));
}
catch (...)
{
......@@ -3494,7 +3494,7 @@ TEST(type_prop, max_pool_invalid_movement_stride_rank)
catch (const ngraph_error& error)
{
EXPECT_EQ(error.what(),
std::string("Max pool window movement stride rank does not "
std::string("Max-pool window movement stride rank does not "
"match number of spatial dimensions."));
}
catch (...)
......@@ -3517,7 +3517,8 @@ TEST(type_prop, max_pool_invalid_input_data_size_0)
}
catch (const ngraph_error& error)
{
EXPECT_EQ(error.what(), std::string("Max pool input spatial dimension is zero."));
EXPECT_EQ(error.what(),
std::string("Max-pool input spatial dimension is zero even after padding."));
}
catch (...)
{
......@@ -3539,7 +3540,7 @@ TEST(type_prop, max_pool_invalid_window_size_0)
}
catch (const ngraph_error& error)
{
EXPECT_EQ(error.what(), std::string("Max pool window shape has a zero-length axis."));
EXPECT_EQ(error.what(), std::string("Max-pool window shape has a zero-length axis."));
}
catch (...)
{
......@@ -3561,8 +3562,10 @@ TEST(type_prop, max_pool_invalid_dilated_too_large)
}
catch (const ngraph_error& error)
{
EXPECT_EQ(error.what(),
std::string("Max pool window shape is larger than the spatial dimensions."));
EXPECT_EQ(
error.what(),
std::string(
"Max-pool window shape is larger than the spatial dimensions even after padding."));
}
catch (...)
{
......@@ -3585,7 +3588,7 @@ TEST(type_prop, max_pool_invalid_movement_stride_0)
}
catch (const ngraph_error& error)
{
EXPECT_EQ(error.what(), std::string("Max pool window axis movement stride is zero."));
EXPECT_EQ(error.what(), std::string("Max-pool window axis movement stride is zero."));
}
catch (...)
{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment