[loco] Introduce Feature Permuting Codec (#3517)
author박종현/On-Device Lab(SR)/Staff Engineer/삼성전자 <jh1302.park@samsung.com>
Mon, 20 May 2019 03:43:35 +0000 (12:43 +0900)
committerGitHub Enterprise <noreply-CODE@samsung.com>
Mon, 20 May 2019 03:43:35 +0000 (12:43 +0900)
* [loco] Introduce Feature Permuting Codec

This commit implements a basic feature codec based on axis permutation.

Signed-off-by: Jonghyun Park <jh1302.park@samsung.com>
* Update comments & add a missing assert message

* Check whether permutation axes are all distinct

* Fix a typo (Encoder -> Decoder)

contrib/loco/include/loco/IR/FeatureAxis.h [new file with mode: 0644]
contrib/loco/include/loco/IR/PermutingCodec.h [new file with mode: 0644]
contrib/loco/src/IR/FeatureAxis.cpp [new file with mode: 0644]
contrib/loco/src/IR/PermutingCodec.cpp [new file with mode: 0644]
contrib/loco/src/IR/PermutingCodec.test.cpp [new file with mode: 0644]

diff --git a/contrib/loco/include/loco/IR/FeatureAxis.h b/contrib/loco/include/loco/IR/FeatureAxis.h
new file mode 100644 (file)
index 0000000..cf020ed
--- /dev/null
@@ -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 (file)
index 0000000..7798448
--- /dev/null
@@ -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 <map>
+
+namespace loco
+{
+
+using TensorAxis = uint32_t;
+
+template <Domain D> class Permutation;
+template <Domain D> class PermutingEncoder;
+template <Domain D> class PermutingDecoder;
+
+/**
+ * @brief Mapping between Feature/Tensor Axis
+ */
+template <> class Permutation<Domain::Feature>
+{
+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<FeatureAxis, TensorAxis> _map;
+};
+
+template <> class PermutingEncoder<Domain::Feature> 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<Domain::Feature> *perm(void) const { return &_perm; }
+  Permutation<Domain::Feature> *perm(void) { return &_perm; }
+
+private:
+  Permutation<Domain::Feature> _perm;
+};
+
+template <> class PermutingDecoder<Domain::Feature> 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<Domain::Feature> *perm(void) const { return &_perm; }
+  Permutation<Domain::Feature> *perm(void) { return &_perm; }
+
+private:
+  Permutation<Domain::Feature> _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 (file)
index 0000000..b0f5606
--- /dev/null
@@ -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 (file)
index 0000000..9afa199
--- /dev/null
@@ -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 <cassert>
+#include <set>
+#include <stdexcept>
+
+/**
+ * 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<loco::Domain::Feature> &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<loco::TensorAxis> 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<Domain::Feature>::mapped(const FeatureAxis &axis_f) const
+{
+  assert(valid(axis_f) && "invalid feature axis");
+  return _map.find(axis_f) != _map.end();
+}
+
+uint32_t Permutation<Domain::Feature>::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<Domain::Feature>::axis(const FeatureAxis &axis_f)
+{
+  assert(valid(axis_f) && "invalid feature axis");
+  return _map[axis_f];
+}
+
+//
+// Permuting Encoder
+//
+FeatureShape PermutingEncoder<Domain::Feature>::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<Domain::Feature>::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<Domain::Feature>::valid(void) const { return ::valid(_perm); }
+
+//
+// Permuting Decoder
+//
+TensorShape PermutingDecoder<Domain::Feature>::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<Domain::Feature>::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<Domain::Feature>::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 (file)
index 0000000..2d7c3f1
--- /dev/null
@@ -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 <gtest/gtest.h>
+
+using namespace loco;
+
+TEST(PemutationTest, feature)
+{
+  Permutation<Domain::Feature> 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<Domain::Feature> 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<Domain::Feature> 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);
+}