[Realizer] Change input connection semantics
authorJihoon Lee <jhoon.it.lee@samsung.com>
Thu, 16 Dec 2021 13:54:18 +0000 (22:54 +0900)
committerJijoong Moon <jijoong.moon@samsung.com>
Tue, 28 Dec 2021 12:42:16 +0000 (21:42 +0900)
Input connection semantic changed to be more intuitive by mapping
connection one to one

**Self evaluation:**
1. Build test: [X]Passed [ ]Failed [ ]Skipped
2. Run test: [X]Passed [ ]Failed [ ]Skipped

Signed-off-by: Jihoon Lee <jhoon.it.lee@samsung.com>
nntrainer/compiler/input_realizer.cpp
nntrainer/compiler/input_realizer.h
nntrainer/models/neuralnet.cpp
test/unittest/compiler/unittest_realizer.cpp

index a842c78..e0ba2c2 100644 (file)
@@ -9,19 +9,24 @@
  * @author Jihoon Lee <jhoon.it.lee@samsung.com>
  * @bug No known bugs except for NYI items
  */
+#include <connection.h>
 #include <input_realizer.h>
 #include <layer_node.h>
 #include <nntrainer_error.h>
 #include <nntrainer_log.h>
 
 #include <algorithm>
+#include <stdexcept>
 #include <unordered_map>
 
 namespace nntrainer {
-InputRealizer::InputRealizer(const std::vector<std::string> &start_layers,
-                             const std::vector<std::string> &input_layers) :
-  start_layers(start_layers),
-  input_layers(input_layers) {}
+InputRealizer::InputRealizer(const std::vector<Connection> &start_conns,
+                             const std::vector<Connection> &input_conns) :
+  start_conns(start_conns),
+  input_conns(input_conns) {
+  NNTR_THROW_IF(start_conns.size() != input_conns.size(), std::invalid_argument)
+    << "start connection size is not same input_conns size";
+}
 
 InputRealizer::~InputRealizer() {}
 
@@ -34,38 +39,24 @@ InputRealizer::realize(const GraphRepresentation &reference) {
     std::inserter(existing_nodes, existing_nodes.end()),
     [](auto &node) { return std::pair(node->getName(), node.get()); });
 
-  /// if start_layer is empty, it's not a hard error but likely to be wrong if
-  /// there is two inputs
-  ml_logw("trying to realize without start_layer specified, if there is more "
-          "than two inputs, sort order make setting graph not determinated");
-
-  auto get_next_input_ref = [input_ref_iter = input_layers.begin(),
-                             this]() mutable {
-    NNTR_THROW_IF(input_ref_iter == input_layers.end(), std::invalid_argument)
-      << "there is no more input layers";
-    return input_ref_iter++;
-  };
-
-  for (auto &start_name : start_layers) {
-    auto node = existing_nodes.at(start_name);
-
-    auto num_input = node->getNumInputConnections();
+  for (unsigned i = 0u, sz = start_conns.size(); i < sz; ++i) {
+    const auto &sc = start_conns[i];
+    const auto &ic = input_conns[i];
+    auto node = existing_nodes.at(sc.getName());
 
-    if (num_input == 0) {
-      // case1. There is no input layers presented -> push single input
-      node->setProperty({"input_layers=" + *get_next_input_ref()});
+    auto num_connection = node->getNumInputConnections();
+    if (num_connection == 0) {
+      NNTR_THROW_IF(sc.getIndex() != 0, std::invalid_argument)
+        << "start connection: " << sc.toString()
+        << " not defined and num connection of that node is empty, although "
+           "start connection of index zero is allowed";
+      node->setProperty({"input_layers=" + ic.toString()});
     } else {
-      /// case2. There is multiple input layers -> substitute orphaned node
-      /// Orphaned node probably is being created from slicing or it is also a
-      /// possible scenario that the graph in the first place is designed to
-      /// have a orphaned node. In the latter case, the graph was non-compilable
-      /// from the first time.
-      for (auto i = 0u; i < num_input; ++i) {
-        auto name = node->getInputConnectionName(i);
-        if (!existing_nodes.count(name)) {
-          node->setInputConnectionName(i, *get_next_input_ref());
-        }
-      }
+      NNTR_THROW_IF(sc.getIndex() >= num_connection, std::invalid_argument)
+        << "start connection: " << sc.toString()
+        << " not defined, num connection: " << num_connection;
+      node->setInputConnectionName(sc.getIndex(), ic.getName());
+      node->setInputConnectionIndex(sc.getIndex(), ic.getIndex());
     }
   }
 
index 530a7e6..0825be6 100644 (file)
 #include <string>
 #include <vector>
 
+#include <connection.h>
 #include <realizer.h>
 
 namespace nntrainer {
 
 /**
  * @brief Graph realizer class which remaps input from start -> input layers
- * @note This class find orphaned identifer in order from start_layers and
- * change the identifier to input_layers. If start_layers does not have any
- * input layers, push single input identifier, if start_layers have
- * input_layers, check if the given input layer exists starting from the first
- * input layers, if not exist, change to the given input layer in order. In case
- * of start_layer contains n input_layers to be replaced.
+ * @note This class overwrites input conns to the location of start conns.
+ * This requires each start location have the slot for input connection.
+ * @note When number of input connection == 0 for a start connection of index
+ * zero, this pushes input connection to the slot
  *
  */
 class InputRealizer final : public GraphRealizer {
@@ -35,11 +34,11 @@ public:
   /**
    * @brief Construct a new Input Realizer object
    *
-   * @param start_layers start layers
-   * @param input_layers input layers
+   * @param start_conns start layers
+   * @param input_conns input layers
    */
-  InputRealizer(const std::vector<std::string> &start_layers,
-                const std::vector<std::string> &input_layers);
+  InputRealizer(const std::vector<Connection> &start_conns,
+                const std::vector<Connection> &input_conns);
 
   /**
    * @brief Destroy the Graph Realizer object
@@ -57,8 +56,8 @@ public:
   GraphRepresentation realize(const GraphRepresentation &reference) override;
 
 private:
-  std::vector<std::string> start_layers;
-  std::vector<std::string> input_layers;
+  std::vector<Connection> start_conns;
+  std::vector<Connection> input_conns;
 };
 
 } // namespace nntrainer
index 59fc886..36b9fcc 100644 (file)
@@ -945,7 +945,8 @@ void NeuralNetwork::addWithReferenceLayers(
 
   auto start_conns =
     std::vector<Connection>(start_layers.begin(), start_layers.end());
-
+  auto input_conns =
+    std::vector<Connection>(input_layers.begin(), input_layers.end());
   auto end_conns =
     std::vector<Connection>(end_layers.begin(), end_layers.end());
 
@@ -955,7 +956,7 @@ void NeuralNetwork::addWithReferenceLayers(
   realizers.emplace_back(new SliceRealizer(start_conns, end_conns));
 
   if (!input_layers_.empty()) {
-    realizers.emplace_back(new InputRealizer(start_layers_, input_layers_));
+    realizers.emplace_back(new InputRealizer(start_conns, input_conns));
   }
 
   if (type == ml::train::ReferenceLayersType::RECURRENT) {
index 5385e47..4fa973f 100644 (file)
@@ -514,28 +514,110 @@ TEST(SliceRealizer, slice_02_p) {
   realizeAndEqual(r, before, after);
 }
 
-TEST(InputRealizer, remap_p) {
+TEST(InputRealizer, input_p) {
   std::vector<LayerRepresentation> before = {
-    {"fully_connected", {"name=fc1"}}, // no input_layers specified
-    {"fully_connected",
-     {"name=fc2", "input_layers=none1,fc1"}}, // single orphaned node
-    {"fully_connected",
-     {"name=fc3", "input_layers=none2,fc2,none3"}}, // multi orphaned node
+    {"fully_connected", {"name=fc1"}},
+    {"fully_connected", {"name=fc2", "input_layers=none1,fc1"}},
+    {"fully_connected", {"name=fc3", "input_layers=none2,fc2,none3"}},
   };
 
   std::vector<LayerRepresentation> after = {
-    {"fully_connected",
-     {"name=fc1", "input_layers=in1"}}, // no input_layers specified
-    {"fully_connected",
-     {"name=fc2", "input_layers=in2,fc1"}}, // single orphaned node
-    {"fully_connected",
-     {"name=fc3", "input_layers=in3,fc2,in4"}}, // multi orphaned node
+    {"fully_connected", {"name=fc1", "input_layers=in1(0)"}},
+    {"fully_connected", {"name=fc2", "input_layers=in2,fc1"}},
+    {"fully_connected", {"name=fc3", "input_layers=in3(3),fc2,in4"}},
   };
 
-  InputRealizer r({"fc1", "fc2", "fc3"}, {"in1", "in2", "in3", "in4"});
+  using C = Connection;
+  InputRealizer r(
+    {
+      C("fc1"),
+      C("fc2(0)"),
+      C("fc3(0)"),
+      C("fc3(2)"),
+    },
+    {
+      C("in1(0)"),
+      C("in2"),
+      C("in3(3)"),
+      C("in4"),
+    });
   realizeAndEqual(r, before, after);
 }
 
+TEST(InputRealizer, input_start_num_not_match_n) {
+  using C = Connection;
+  EXPECT_ANY_THROW(InputRealizer r(
+    {
+      C("fc1"),
+    },
+    {
+      C("in1(0)"),
+      C("in2"),
+      C("in3(3)"),
+      C("in4"),
+    }));
+}
+
+TEST(InputRealizer, start_empty_conn_not_defined_n) {
+  std::vector<LayerRepresentation> before = {
+    {"fully_connected", {"name=fc1"}},
+    {"fully_connected", {"name=fc2", "input_layers=none1,fc1"}},
+    {"fully_connected", {"name=fc3", "input_layers=none2,fc2,none3"}},
+  };
+
+  std::vector<LayerRepresentation> after = {
+    {"fully_connected", {"name=fc1", "input_layers=in1(0)"}},
+    {"fully_connected", {"name=fc2", "input_layers=in2,fc1"}},
+    {"fully_connected", {"name=fc3", "input_layers=in3(3),fc2,in4"}},
+  };
+
+  using C = Connection;
+  InputRealizer r(
+    {
+      C("fc1(2)"), /**< connection not defined, although fc1(0) is allowed */
+      C("fc2(0)"),
+      C("fc3(0)"),
+      C("fc3(2)"),
+    },
+    {
+      C("in1(0)"),
+      C("in2"),
+      C("in3(3)"),
+      C("in4"),
+    });
+  EXPECT_ANY_THROW(realizeAndEqual(r, before, after));
+}
+
+TEST(InputRealizer, intermediate_conn_not_defined_n) {
+  std::vector<LayerRepresentation> before = {
+    {"fully_connected", {"name=fc1"}},
+    {"fully_connected", {"name=fc2", "input_layers=none1,fc1"}},
+    {"fully_connected", {"name=fc3", "input_layers=none2,fc2,none3"}},
+  };
+
+  std::vector<LayerRepresentation> after = {
+    {"fully_connected", {"name=fc1", "input_layers=in1(0)"}},
+    {"fully_connected", {"name=fc2", "input_layers=in2,fc1"}},
+    {"fully_connected", {"name=fc3", "input_layers=in3(3),fc2,in4"}},
+  };
+
+  using C = Connection;
+  InputRealizer r(
+    {
+      C("fc1"),
+      C("fc2(4)"), /**< connection not defined */
+      C("fc3(0)"),
+      C("fc3(2)"),
+    },
+    {
+      C("in1(0)"),
+      C("in2"),
+      C("in3(3)"),
+      C("in4"),
+    });
+  EXPECT_ANY_THROW(realizeAndEqual(r, before, after));
+}
+
 TEST(PreviousInputRealizer, previous_p) {
   { /// realization without identifying custom input
     std::vector<LayerRepresentation> before = {