From: 박종현/On-Device Lab(SR)/Staff Engineer/삼성전자 Date: Mon, 20 May 2019 03:43:35 +0000 (+0900) Subject: [loco] Introduce Feature Permuting Codec (#3517) X-Git-Tag: nncc_backup~566 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=a511bb951ffde43939a2af1f65f6da438af72fb2;p=platform%2Fcore%2Fml%2Fnnfw.git [loco] Introduce Feature Permuting Codec (#3517) * [loco] Introduce Feature Permuting Codec This commit implements a basic feature codec based on axis permutation. Signed-off-by: Jonghyun Park * Update comments & add a missing assert message * Check whether permutation axes are all distinct * Fix a typo (Encoder -> Decoder) --- diff --git a/contrib/loco/include/loco/IR/FeatureAxis.h b/contrib/loco/include/loco/IR/FeatureAxis.h new file mode 100644 index 0000000..cf020ed --- /dev/null +++ b/contrib/loco/include/loco/IR/FeatureAxis.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 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 __LOCO_IR_FEATURE_AXIS_H__ +#define __LOCO_IR_FEATURE_AXIS_H__ + +namespace loco +{ + +enum class FeatureAxis +{ + Count, + Depth, + Height, + Width +}; + +} // namespace loco + +#endif // __LOCO_IR_FEATURE_AXIS_H__ diff --git a/contrib/loco/include/loco/IR/PermutingCodec.h b/contrib/loco/include/loco/IR/PermutingCodec.h new file mode 100644 index 0000000..7798448 --- /dev/null +++ b/contrib/loco/include/loco/IR/PermutingCodec.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019 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 __LOCO_IR_PERMUTING_CODEC_H__ +#define __LOCO_IR_PERMUTING_CODEC_H__ + +#include "loco/IR/Domain.h" + +#include "loco/IR/FeatureAxis.h" +#include "loco/IR/FeatureCodec.h" + +#include + +namespace loco +{ + +using TensorAxis = uint32_t; + +template class Permutation; +template class PermutingEncoder; +template class PermutingDecoder; + +/** + * @brief Mapping between Feature/Tensor Axis + */ +template <> class Permutation +{ +public: + Permutation() = default; + +public: + /** + * @brief Return whether a tensor axis is specified for a given feature axis + * + * This method does not validate the corresponding value. + */ + bool mapped(const FeatureAxis &axis_f) const; + + /** + * @brief Get the tensor axis corresponding to a given feature axis + * + * This method works correclty only when feature axis is mapped before. + */ + TensorAxis axis(const FeatureAxis &axis_f) const; + + /** + * @brief Set the tensor axis corresponding to a given feature axis + */ + TensorAxis &axis(const FeatureAxis &axis_f); + + TensorAxis operator[](const FeatureAxis &axis_f) const { return axis(axis_f); } + TensorAxis &operator[](const FeatureAxis &axis_f) { return axis(axis_f); } + +private: + std::map _map; +}; + +template <> class PermutingEncoder final : public FeatureEncoder +{ +public: + PermutingEncoder() = default; + +public: + bool valid(void) const; + +public: + FeatureShape shape(const TensorShape &tensor_shape) const override; + TensorIndex value(const FeatureIndex &index) const override; + +public: + const Permutation *perm(void) const { return &_perm; } + Permutation *perm(void) { return &_perm; } + +private: + Permutation _perm; +}; + +template <> class PermutingDecoder final : public FeatureDecoder +{ +public: + PermutingDecoder() = default; + +public: + bool valid(void) const; + +public: + TensorShape shape(const FeatureShape &tensor_shape) const override; + FeatureIndex value(const TensorIndex &index) const override; + +public: + const Permutation *perm(void) const { return &_perm; } + Permutation *perm(void) { return &_perm; } + +private: + Permutation _perm; +}; + +} // namespace loco + +#endif // __LOCO_IR_PERMUTING_CODEC_H__ diff --git a/contrib/loco/src/IR/FeatureAxis.cpp b/contrib/loco/src/IR/FeatureAxis.cpp new file mode 100644 index 0000000..b0f5606 --- /dev/null +++ b/contrib/loco/src/IR/FeatureAxis.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 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 "loco/IR/FeatureAxis.h" + +// NOTE This file validates "FeatureAxis.h". Please DO NOT remove this file. diff --git a/contrib/loco/src/IR/PermutingCodec.cpp b/contrib/loco/src/IR/PermutingCodec.cpp new file mode 100644 index 0000000..9afa199 --- /dev/null +++ b/contrib/loco/src/IR/PermutingCodec.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2019 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 "loco/IR/PermutingCodec.h" + +#include +#include +#include + +/** + * Feature Domain + */ +namespace +{ + +using loco::FeatureAxis; + +inline bool valid(const FeatureAxis &axis) +{ + switch (axis) + { + case FeatureAxis::Count: + return true; + case FeatureAxis::Depth: + return true; + case FeatureAxis::Height: + return true; + case FeatureAxis::Width: + return true; + default: + break; + } + + return false; +} + +inline bool valid(const loco::Permutation &perm) +{ + auto check = [&perm](FeatureAxis axis_f) { + if (!perm.mapped(axis_f)) + return false; + return perm.axis(axis_f) < 4; + }; + + if (!check(FeatureAxis::Count)) + return false; + if (!check(FeatureAxis::Depth)) + return false; + if (!check(FeatureAxis::Height)) + return false; + if (!check(FeatureAxis::Width)) + return false; + + // Check whether tensor axes are all distinct + std::set values; + + values.insert(perm[FeatureAxis::Count]); + values.insert(perm[FeatureAxis::Depth]); + values.insert(perm[FeatureAxis::Height]); + values.insert(perm[FeatureAxis::Width]); + + return values.size() == 4; +} + +} // namespace + +namespace loco +{ + +// +// Permutation +// +bool Permutation::mapped(const FeatureAxis &axis_f) const +{ + assert(valid(axis_f) && "invalid feature axis"); + return _map.find(axis_f) != _map.end(); +} + +uint32_t Permutation::axis(const FeatureAxis &axis_f) const +{ + assert(valid(axis_f) && "invalid feature axis"); + assert(mapped(axis_f) && "unmapped feature axis"); + return _map.at(axis_f); +} + +uint32_t &Permutation::axis(const FeatureAxis &axis_f) +{ + assert(valid(axis_f) && "invalid feature axis"); + return _map[axis_f]; +} + +// +// Permuting Encoder +// +FeatureShape PermutingEncoder::shape(const TensorShape &in) const +{ + assert(valid() && "invalid permutation"); + + FeatureShape out; + + out.count() = in.dim(_perm[FeatureAxis::Count]); + out.depth() = in.dim(_perm[FeatureAxis::Depth]); + out.height() = in.dim(_perm[FeatureAxis::Height]); + out.width() = in.dim(_perm[FeatureAxis::Width]); + + return out; +} + +TensorIndex PermutingEncoder::value(const FeatureIndex &in) const +{ + assert(valid() && "invalid permutation"); + + TensorIndex out; + + out.resize(4); + + out.at(_perm[FeatureAxis::Count]) = in.batch(); + out.at(_perm[FeatureAxis::Depth]) = in.channel(); + out.at(_perm[FeatureAxis::Height]) = in.row(); + out.at(_perm[FeatureAxis::Width]) = in.column(); + + return out; +} + +bool PermutingEncoder::valid(void) const { return ::valid(_perm); } + +// +// Permuting Decoder +// +TensorShape PermutingDecoder::shape(const FeatureShape &in) const +{ + assert(valid() && "invalid permuation"); + + TensorShape out; + + out.rank(4); + + out.dim(_perm[FeatureAxis::Count]) = in.count(); + out.dim(_perm[FeatureAxis::Depth]) = in.depth(); + out.dim(_perm[FeatureAxis::Height]) = in.height(); + out.dim(_perm[FeatureAxis::Width]) = in.width(); + + return out; +} + +FeatureIndex PermutingDecoder::value(const TensorIndex &in) const +{ + assert(valid() && "invalid permutation"); + + FeatureIndex out; + + out.batch() = in.at(_perm[FeatureAxis::Count]); + out.channel() = in.at(_perm[FeatureAxis::Depth]); + out.row() = in.at(_perm[FeatureAxis::Height]); + out.column() = in.at(_perm[FeatureAxis::Width]); + + return out; +} + +bool PermutingDecoder::valid(void) const { return ::valid(_perm); } + +} // namespace loco diff --git a/contrib/loco/src/IR/PermutingCodec.test.cpp b/contrib/loco/src/IR/PermutingCodec.test.cpp new file mode 100644 index 0000000..2d7c3f1 --- /dev/null +++ b/contrib/loco/src/IR/PermutingCodec.test.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2019 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 "loco/IR/PermutingCodec.h" + +#include + +using namespace loco; + +TEST(PemutationTest, feature) +{ + Permutation perm; + + // All values are invalid at the beginning + ASSERT_FALSE(perm.mapped(FeatureAxis::Count)); + ASSERT_FALSE(perm.mapped(FeatureAxis::Depth)); + ASSERT_FALSE(perm.mapped(FeatureAxis::Height)); + ASSERT_FALSE(perm.mapped(FeatureAxis::Width)); + + // Update mapping + perm[FeatureAxis::Count] = 5; + perm[FeatureAxis::Depth] = 6; + perm[FeatureAxis::Height] = 7; + perm[FeatureAxis::Width] = 8; + + // Now perm has a mapping for all the axes + ASSERT_TRUE(perm.mapped(FeatureAxis::Count)); + ASSERT_TRUE(perm.mapped(FeatureAxis::Depth)); + ASSERT_TRUE(perm.mapped(FeatureAxis::Height)); + ASSERT_TRUE(perm.mapped(FeatureAxis::Width)); + + // Check the value + ASSERT_EQ(perm[FeatureAxis::Count], 5); + ASSERT_EQ(perm[FeatureAxis::Depth], 6); + ASSERT_EQ(perm[FeatureAxis::Height], 7); + ASSERT_EQ(perm[FeatureAxis::Width], 8); +} + +TEST(PermutingEncoderTest, feature) +{ + PermutingEncoder enc; + + // Encoder is invalid at the beginning + ASSERT_FALSE(enc.valid()); + + // Set "invalid" mapping + enc.perm()->axis(FeatureAxis::Count) = 0; + enc.perm()->axis(FeatureAxis::Depth) = 6; + enc.perm()->axis(FeatureAxis::Height) = 1; + enc.perm()->axis(FeatureAxis::Width) = 2; + + // Encoder is still invalid + ASSERT_FALSE(enc.valid()); + + // Set another "invalid" mapping + enc.perm()->axis(FeatureAxis::Depth) = 1; + + // Encoder is still invalid + ASSERT_FALSE(enc.valid()); + + // Set "valid" mapping + enc.perm()->axis(FeatureAxis::Depth) = 3; + + // Encoder is now valid + ASSERT_TRUE(enc.valid()); + + // Let's test with a HD (1280x720) RGB image + TensorShape tensor_shape; + + tensor_shape.rank(4); + tensor_shape.dim(0) = 1; // COUNT + tensor_shape.dim(1) = 720; // HEIGHT + tensor_shape.dim(2) = 1280; // WIDTH + tensor_shape.dim(3) = 3; // DEPTH + + // Get the feature shape corresponding to a given image + auto feature_shape = enc.shape(tensor_shape); + + ASSERT_EQ(feature_shape.count().value(), 1); + ASSERT_EQ(feature_shape.depth().value(), 3); + ASSERT_EQ(feature_shape.height().value(), 720); + ASSERT_EQ(feature_shape.width().value(), 1280); + + // Let's find a source tensor index! + FeatureIndex feature_index; + + feature_index.batch() = 0; + feature_index.channel() = 1; + feature_index.row() = 2; + feature_index.column() = 3; + + auto tensor_index = enc.value(feature_index); + + ASSERT_EQ(tensor_index.at(0), 0); // BATCH(COUNT) + ASSERT_EQ(tensor_index.at(1), 2); // ROW(HEIGHT) + ASSERT_EQ(tensor_index.at(2), 3); // COLUMN(WIDTH) + ASSERT_EQ(tensor_index.at(3), 1); // CHANNEL(DEPTH) +} + +TEST(PermutingDecoderTest, feature) +{ + PermutingDecoder dec; + + // Decoder is invalid at the beginning + ASSERT_FALSE(dec.valid()); + + // Set "invalid" mapping + dec.perm()->axis(FeatureAxis::Count) = 0; + dec.perm()->axis(FeatureAxis::Depth) = 6; + dec.perm()->axis(FeatureAxis::Height) = 1; + dec.perm()->axis(FeatureAxis::Width) = 2; + + // Decoder is still invalid + ASSERT_FALSE(dec.valid()); + + // Set another "invalid" mapping + dec.perm()->axis(FeatureAxis::Depth) = 1; + + // Decoder is still invalid + ASSERT_FALSE(dec.valid()); + + // Set "valid" mapping + dec.perm()->axis(FeatureAxis::Depth) = 3; + + // Decoder is now valid + ASSERT_TRUE(dec.valid()); + + // Let's test with a HD (1280x720) RGB image + FeatureShape feature_shape; + + feature_shape.count() = 1; + feature_shape.depth() = 3; + feature_shape.height() = 720; + feature_shape.width() = 1280; + + // Get the tensor shape corresponding to a given image + auto tensor_shape = dec.shape(feature_shape); + + ASSERT_EQ(tensor_shape.rank(), 4); + ASSERT_EQ(tensor_shape.dim(0).value(), 1); // COUNT + ASSERT_EQ(tensor_shape.dim(1).value(), 720); // HEIGHT + ASSERT_EQ(tensor_shape.dim(2).value(), 1280); // WIDTH + ASSERT_EQ(tensor_shape.dim(3).value(), 3); // DEPTH + + // Let's find a source feature index! + TensorIndex tensor_index; + + tensor_index.resize(4); + + tensor_index.at(0) = 0; // BATCH(COUNT) + tensor_index.at(3) = 1; // CHANNEL(DEPTH) + tensor_index.at(1) = 2; // ROW(HEIGHT) + tensor_index.at(2) = 3; // COLUMN(WIDTH) + + auto feature_index = dec.value(tensor_index); + + ASSERT_EQ(feature_index.batch(), 0); + ASSERT_EQ(feature_index.channel(), 1); + ASSERT_EQ(feature_index.row(), 2); + ASSERT_EQ(feature_index.column(), 3); +}