diff --git a/compute/cker/include/cker/train/operation/AveragePool.h b/compute/cker/include/cker/train/operation/AveragePool.h new file mode 100644 index 00000000000..7f68845130e --- /dev/null +++ b/compute/cker/include/cker/train/operation/AveragePool.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __NNFW_CKER_TRAIN_OPERATION_AVERAGEPOOL_H__ +#define __NNFW_CKER_TRAIN_OPERATION_AVERAGEPOOL_H__ + +#include "cker/Shape.h" +#include "cker/Utils.h" +#include "cker/eigen/Utils.h" + +#include + +namespace nnfw +{ +namespace cker +{ +namespace train +{ + +inline void AveragePool2DGrad(const PoolParams ¶ms, const Shape &incoming_shape, + const float *incoming_data, const Shape &grad_shape, float *grad_data) +{ + assert(grad_shape.DimensionsCount() == 4); + assert(incoming_shape.DimensionsCount() == 4); + + const int batches = MatchingDim(incoming_shape, 0, grad_shape, 0); + const int grad_height = grad_shape.Dims(1); + const int grad_width = grad_shape.Dims(2); + const int incoming_height = incoming_shape.Dims(1); + const int incoming_width = incoming_shape.Dims(2); + const int stride_height = params.stride_height; + const int stride_width = params.stride_width; + + // initialize grad_data + std::fill(grad_data, grad_data + grad_shape.FlatSize(), 0.0); + + const auto incoming_mat = MapAsMatrixWithLastDimAsRows(incoming_data, incoming_shape); + auto grad_mat = MapAsMatrixWithLastDimAsRows(grad_data, grad_shape); + + for (int b = 0; b < batches; ++b) + { + for (int h = 0; h < incoming_height; ++h) + { + for (int w = 0; w < incoming_width; ++w) + { + // (h_start, h_end) * (w_start, w_end) is input range + // that output is projected from. + int h_start = h * stride_height - params.padding_values.height; + int h_end = std::min(h_start + params.filter_height, grad_height); + h_start = h_start < 0 ? 0 : h_start; + + int w_start = w * stride_width - params.padding_values.width; + int w_end = std::min(w_start + params.filter_width, grad_width); + w_start = w_start < 0 ? 0 : w_start; + + int count = (h_end - h_start) * (w_end - w_start); + + if (h_end <= 0 || w_end <= 0 || count <= 0 || h_start >= grad_height || + w_start >= grad_width) + continue; + + int incoming_offset = NodeOffset(b, h, w, incoming_height, incoming_width); + for (int ph = h_start; ph < h_end; ++ph) + { + for (int pw = w_start; pw < w_end; ++pw) + { + int grad_offset = NodeOffset(b, ph, pw, grad_height, grad_width); + grad_mat.col(grad_offset) += incoming_mat.col(incoming_offset) / count; + } + } + } + } + } +} + +} // namespace train +} // namespace cker +} // namespace nnfw + +#endif // __NNFW_CKER_TRAIN_OPERATION_AVERAGEPOOL_H__ diff --git a/compute/cker/src/train/AveragePool.test.cc b/compute/cker/src/train/AveragePool.test.cc new file mode 100644 index 00000000000..51be5e5edd7 --- /dev/null +++ b/compute/cker/src/train/AveragePool.test.cc @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2024 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include +#include + +namespace +{ +using namespace nnfw::cker; + +template class AvgPoolOpVerifier +{ +private: + const PoolParams _op_params; + const Shape _in_shape; + const Shape _out_shape; + +public: + AvgPoolOpVerifier(const nnfw::cker::PoolParams &op_params, const Shape &in_shape, + const Shape &out_shape) + : _op_params(op_params), _in_shape(in_shape), _out_shape(out_shape) + { + } + +public: + void verifyForward(const std::vector input, const std::vector expected_output, + bool expect_eq = true) + { + assert(input.size() == _in_shape.FlatSize()); + assert(expected_output.size() == _out_shape.FlatSize()); + + std::vector cacluated_output(_out_shape.FlatSize()); + nnfw::cker::AveragePool(_op_params, _in_shape, input.data(), _out_shape, + cacluated_output.data()); + + if (expect_eq) + EXPECT_EQ(expected_output, cacluated_output); + else + EXPECT_NE(expected_output, cacluated_output); + } + + void verifyBackward(const std::vector incoming_data, const std::vector expected_grad_data, + bool expect_eq = true) + { + assert(incoming_data.size() == _out_shape.FlatSize()); + assert(expected_grad_data.size() == _in_shape.FlatSize()); + + std::vector calcuated_grad(_in_shape.FlatSize()); + nnfw::cker::train::AveragePool2DGrad(_op_params, _out_shape, incoming_data.data(), _in_shape, + calcuated_grad.data()); + + if (expect_eq) + { + for (size_t i = 0; i < expected_grad_data.size(); i++) + { + EXPECT_FLOAT_EQ(expected_grad_data[i], calcuated_grad[i]); + } + } + + else + EXPECT_NE(expected_grad_data, calcuated_grad); + } +}; + +} // namespace + +TEST(CKer_Operation, AveragePool2D) +{ + // Depth 1 case + { + nnfw::cker::PoolParams op_param; + { + op_param.stride_height = 1; + op_param.stride_width = 1; + op_param.filter_height = 2; + op_param.filter_width = 2; + op_param.padding_values.height = 0; + op_param.padding_values.width = 0; + op_param.float_activation_max = std::numeric_limits::max(); + op_param.float_activation_min = std::numeric_limits::lowest(); + } + nnfw::cker::Shape in = {1, 3, 3, 1}; + nnfw::cker::Shape out = {1, 2, 2, 1}; + + AvgPoolOpVerifier verifier(op_param, in, out); + + /** + * input : output: + * + * 10(0) 15(1) 2(2) + * 7(3) 8(4) 9(5) - (forward) -> 10(4) 8.5(4) + * 10(6) 1(7) 0(8) 6.5(4) 4.5(4) + */ + + std::vector input = {10, 15, 2, 7, 8, 9, 10, 1, 0}; + std::vector expected_output = {10, 8.5, 6.5, 4.5}; + verifier.verifyForward(input, expected_output); + + /** + * output_deriv: input_deriv: + * + * + * 0.4 0.4 0.1 0.2 0.1 + * 0.4 0.4 - (backward) -> 0.2 0.4 0.2 + * 0.1 0.2 0.1 + */ + + std::vector output_deriv = {0.4, 0.4, 0.4, 0.4}; + std::vector expected_input_deriv = {0.1, 0.2, 0.1, 0.2, 0.4, 0.2, 0.1, 0.2, 0.1}; + verifier.verifyBackward(output_deriv, expected_input_deriv); + } + + // Depth 2 case + { + nnfw::cker::PoolParams op_param; + { + op_param.stride_height = 1; + op_param.stride_width = 1; + op_param.filter_height = 3; + op_param.filter_width = 3; + op_param.padding_values.height = 0; + op_param.padding_values.width = 0; + op_param.float_activation_max = std::numeric_limits::max(); + op_param.float_activation_min = std::numeric_limits::lowest(); + } + nnfw::cker::Shape in = {1, 3, 3, 2}; + nnfw::cker::Shape out = {1, 1, 1, 2}; + + AvgPoolOpVerifier verifier(op_param, in, out); + + /** + * depth[0] + * input : output: + * + * 10(0) 15(1) 2(2) + * 10(3) 12(4) 17(5) -(forward)-> 16(0) + * 50(6) 30(7) -2(8) + * + * + * depth[1] + * input: output: + * + * -1(0) 2(1) 3(2) + * 8(3) 9(4) 2(5) -(forward)-> 4(0) + * 4(6) 2(7) 7(8) + */ + + std::vector input(in.FlatSize()); + auto input_mat = MapAsMatrixWithLastDimAsRows(input.data(), in); + input_mat << /* depth0 */ 10, 15, 2, 10, 12, 17, 50, 30, -2, + /* depth1 */ -1, 2, 3, 8, 9, 2, 4, 2, 7; + std::vector expected_output = {16, 4}; + verifier.verifyForward(input, expected_output); + + /** + * depth[0] + * ouput_deriv: input_deriv: + * + * 0.02 0.02 0.02 + * 0.18 -(backward)-> 0.02 0.02 0.02 + * 0.02 0.02 0.02 + * + * + * depth[1] + * output_deriv: input_deriv: + * 0.04 0.04 0.04 + * 0.36 -(backward)-> 0.04 0.04 0.04 + * 0.04 0.04 0.04 + */ + + std::vector output_deriv = {0.18, 0.36}; + std::vector expected_input_deriv(in.FlatSize()); + auto input_deriv_mat = MapAsMatrixWithLastDimAsRows(expected_input_deriv.data(), in); + input_deriv_mat << /* depth0 */ 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, + /* depth1 */ 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04; + verifier.verifyBackward(output_deriv, expected_input_deriv); + } +} + +TEST(CKer_Operation, neg_AveragePoolInvalidExpectedValue) +{ + // Invalid expected value + { + nnfw::cker::PoolParams op_param; + { + op_param.stride_height = 1; + op_param.stride_width = 1; + op_param.filter_height = 2; + op_param.filter_width = 2; + op_param.padding_values.height = 0; + op_param.padding_values.width = 0; + op_param.float_activation_max = std::numeric_limits::max(); + op_param.float_activation_min = std::numeric_limits::lowest(); + } + nnfw::cker::Shape in = {1, 2, 2, 1}; + nnfw::cker::Shape out = {1, 1, 1, 1}; + + AvgPoolOpVerifier verifier(op_param, in, out); + + std::vector input = {0, 0, 0, 0}; + std::vector expected_output = {-1}; + + verifier.verifyForward(input, expected_output, false); + } + + // Invalid expected value + { + nnfw::cker::PoolParams op_param; + { + op_param.stride_height = 2; + op_param.stride_width = 2; + op_param.filter_height = 2; + op_param.filter_width = 2; + op_param.padding_values.height = 1; + op_param.padding_values.width = 1; + op_param.float_activation_max = std::numeric_limits::max(); + op_param.float_activation_min = std::numeric_limits::lowest(); + } + + nnfw::cker::Shape in = {1, 2, 2, 1}; + nnfw::cker::Shape out = {1, 2, 2, 1}; + + AvgPoolOpVerifier verifier(op_param, in, out); + + std::vector input = {0, 0, 0, 0}; + std::vector expected_output = {0, 0, 0, 0}; + verifier.verifyForward(input, expected_output); + + std::vector output_deriv = {0.1, 0.1, 0.1, 0.2}; + std::vector expected_input_deriv = {0.1, 0.1, 0.1, 0.1}; + verifier.verifyBackward(output_deriv, expected_input_deriv, false); + } +}