[ Layer ] Add input_layers keyword
authorjijoong.moon <jijoong.moon@samsung.com>
Thu, 29 Oct 2020 07:10:55 +0000 (16:10 +0900)
committerJijoong Moon <jijoong.moon@samsung.com>
Thu, 5 Nov 2020 07:22:48 +0000 (16:22 +0900)
This PR includes enabling 'input_layers' keyword.
With this keyword, we could specify layer's input tensor.

. Added skip in parse_util to remove space and '[',']' in string and
split with ',' delimiter.

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

Signed-off-by: jijoong.moon <jijoong.moon@samsung.com>
api/ccapi/include/layer.h
nntrainer/include/parse_util.h
nntrainer/src/layer.cpp
nntrainer/src/parse_util.cpp
test/unittest/meson.build
test/unittest/unittest_nntrainer_graph.cpp [new file with mode: 0644]

index a09e2da..4c34cc4 100644 (file)
@@ -90,6 +90,8 @@ public:
    *            22. moving_variance_initializer : string (type),
    *            23. gamma_initializer : string (type),
    *            24. beta_initializer" : string (type)
+   *            25. modelfile : model file for loading config for backbone layer
+   *            26. input_layers" : string (type)
    */
   enum class PropertyType {
     input_shape = 0,
@@ -118,6 +120,7 @@ public:
     gamma_initializer = 23,
     beta_initializer = 24,
     modelfile = 25, /** model file for loading config for backbone layer */
+    input_layers = 26,
     unknown
   };
 
index 76652e2..ff50b52 100644 (file)
@@ -25,6 +25,7 @@
 #ifdef __cplusplus
 
 #include <iostream>
+#include <regex>
 #include <string>
 #include <vector>
 
@@ -180,6 +181,14 @@ const char *getValues(std::vector<int> values, const char *delimiter = ",");
 int getValues(int n_str, std::string str, int *value);
 
 /**
+ * @brief     split string into vector with delimiter regex
+ * @param[in] str string
+ * @param[in] reg regular expression to use as delimiter
+ * @retval    output string vector
+ */
+std::vector<std::string> split(const std::string &s, std::regex &reg);
+
+/**
  * @brief     print instance info. as <Type at (address)>
  * @param[in] std::ostream &out, T&& t
  * @param[in] t pointer to the instance
index 9280da1..a09f4df 100644 (file)
@@ -198,6 +198,16 @@ void Layer::setProperty(const PropertyType type, const std::string &value) {
       bias_initializer = (WeightInitializer)parseType(value, TOKEN_WEIGHT_INIT);
     }
     break;
+  case PropertyType::input_layers:
+    if (!value.empty()) {
+      std::regex reg("\\,+");
+      std::vector<std::string> concat_layers = split(value, reg);
+      // TODO set num_inputs properly
+      num_inputs = 1;
+      if (concat_layers.size() > 1)
+        num_inputs = concat_layers.size();
+    }
+    break;
   default:
     std::string msg =
       "[Layer] Unknown Layer Property Key for value " + std::string(value);
index 7639bd5..65d99ba 100644 (file)
 #include <optimizer_internal.h>
 #include <parse_util.h>
 #include <pooling2d_layer.h>
-#include <regex>
 #include <sstream>
 #include <string>
 
+#define NUM_SKIP_CHAR 3
+
 namespace nntrainer {
 
 int getKeyValue(std::string input_str, std::string &key, std::string &value) {
@@ -276,6 +277,7 @@ unsigned int parseType(std::string ll, InputType t) {
  * moving_variance_initializer = 22
  * gamma_initializer = 23
  * beta_initializer = 24
+ * input_layers = 25
  *
  * InputLayer has 0, 1, 2, 3 properties.
  * FullyConnectedLayer has 1, 4, 6, 7, 8, 9 properties.
@@ -283,7 +285,7 @@ unsigned int parseType(std::string ll, InputType t) {
  * Pooling2DLayer has 12, 13, 14, 15 properties.
  * BatchNormalizationLayer has 0, 1, 5, 6, 7 properties.
  */
-static std::array<std::string, 27> property_string = {
+static std::array<std::string, 28> property_string = {
   "input_shape",
   "normalization",
   "standardization",
@@ -310,6 +312,7 @@ static std::array<std::string, 27> property_string = {
   "gamma_initializer",
   "beta_initializer",
   "modelfile",
+  "input_layers",
   "unknown"};
 
 unsigned int parseLayerProperty(std::string property) {
@@ -498,4 +501,23 @@ const char *getValues(std::vector<int> values, const char *delimiter) {
   return std::move(vec_str.str().c_str());
 }
 
+std::vector<std::string> split(const std::string &s, std::regex &reg) {
+  std::vector<std::string> out;
+  char char_to_remove[NUM_SKIP_CHAR] = {' ', '[', ']'};
+  std::string str = s;
+  for (unsigned int i = 0; i < NUM_SKIP_CHAR; ++i) {
+    str.erase(std::remove(str.begin(), str.end(), char_to_remove[i]),
+              str.end());
+  }
+  std::regex_token_iterator<std::string::iterator> end;
+  std::regex_token_iterator<std::string::iterator> iter(str.begin(), str.end(),
+                                                        reg, -1);
+
+  while (iter != end) {
+    out.push_back(*iter);
+    ++iter;
+  }
+  return out;
+}
+
 } /* namespace nntrainer */
index 218cdf5..67e19a0 100644 (file)
@@ -31,7 +31,8 @@ test_target = [
   'unittest_util_func',
   'unittest_databuffer_file',
   'unittest_nntrainer_modelfile',
-  'unittest_nntrainer_models'
+  'unittest_nntrainer_models',
+  'unittest_nntrainer_graph'
 ]
 
 foreach target: test_target
diff --git a/test/unittest/unittest_nntrainer_graph.cpp b/test/unittest/unittest_nntrainer_graph.cpp
new file mode 100644 (file)
index 0000000..adde42b
--- /dev/null
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: Apache-2.0
+/**
+ * Copyright (C) 2020 Jijoong Moon <jijoong.moon@samsung.com>
+ *
+ * @file unittest_nntrainer_graph.cpp
+ * @date 29 Oct 2020
+ * @brief NNTrainer graph test.
+ * @see        https://github.com/nnstreamer/nntrainer
+ * @author Jijoong Moon <jijoong.moon@samsung.com>
+ * @bug No known bugs except for NYI items
+ */
+
+#include <gtest/gtest.h>
+#include <neuralnet.h>
+
+#include "nntrainer_test_util.h"
+
+namespace initest {
+typedef enum {
+  LOAD = 1 << 0, /**< should fail at load */
+  INIT = 1 << 1, /**< should fail at init */
+} IniFailAt;
+};
+
+class nntrainerGraphTest
+  : public ::testing::TestWithParam<
+      std::tuple<const char *, const IniTestWrapper::Sections, int>> {
+
+protected:
+  virtual void SetUp() {
+    name = std::string(std::get<0>(GetParam()));
+    std::cout << "starting test case : " << name << std::endl << std::endl;
+
+    auto sections = std::get<1>(GetParam());
+
+    ini = IniTestWrapper(name, sections);
+
+    failAt = std::get<2>(GetParam());
+    ini.save_ini();
+  }
+
+  virtual void TearDown() { ini.erase_ini(); }
+
+  std::string getIniName() { return ini.getIniName(); }
+
+  bool failAtLoad() { return failAt & initest::IniFailAt::LOAD; }
+
+  bool failAtInit() { return failAt & initest::IniFailAt::INIT; }
+
+  nntrainer::NeuralNetwork NN;
+
+private:
+  int failAt;
+  std::string name;
+  IniTestWrapper ini;
+};
+
+/**
+ * @brief check given ini is failing/suceeding at load
+ */
+TEST_P(nntrainerGraphTest, loadConfig) {
+  std::cout << std::get<0>(GetParam()) << std::endl;
+  int status = NN.loadFromConfig(getIniName());
+
+  if (failAtLoad()) {
+    EXPECT_NE(status, ML_ERROR_NONE);
+  } else {
+    EXPECT_EQ(status, ML_ERROR_NONE);
+  }
+}
+
+static IniSection nw_base("model", "Type = NeuralNetwork | "
+                                   "batch_size = 32 | "
+                                   "epsilon = 1e-7 | "
+                                   "loss = cross");
+
+static IniSection nw_sgd = nw_base + "Optimizer = sgd |"
+                                     "Learning_rate = 1";
+
+static IniSection input0("inputlayer0", "Type = input |"
+                                        "Input_Shape = 1:1:62720 |"
+                                        "bias_initializer = zeros |"
+                                        "Normalization = true |"
+                                        "Activation = sigmoid");
+
+static IniSection input1("inputlayer1", "Type = input |"
+                                        "Input_Shape = 1:1:62720 |"
+                                        "bias_initializer = zeros |"
+                                        "Normalization = true |"
+                                        "Activation = sigmoid");
+
+static IniSection conv2d("conv2d", "Type = conv2d |"
+                                   "input_layers=inputlayer0, inputlayer1 |"
+                                   "bias_initializer = zeros |"
+                                   "Activation = sigmoid |"
+                                   "filters = 6 |"
+                                   "kernel_size = 5,5 |"
+                                   "stride = 1,1 |"
+                                   "padding = 0,0 |");
+
+static int SUCCESS = 0;
+static int LOADFAIL = initest::LOAD;
+static int INITFAIL = initest::INIT;
+static int ALLFAIL = LOADFAIL | INITFAIL;
+
+using I = IniSection;
+
+/**
+ * @brief make ini test case from given parameter
+ */
+std::tuple<const char *, const IniTestWrapper::Sections, int>
+mkIniTc(const char *name, const IniTestWrapper::Sections vec, int flag) {
+  return std::make_tuple(name, vec, flag);
+}
+
+INSTANTIATE_TEST_CASE_P(
+  nntrainerIniAutoTests, nntrainerGraphTest,
+  ::testing::Values(mkIniTc("basic_p", {nw_sgd, input0, input1, conv2d},
+                            SUCCESS)));
+
+int main(int argc, char **argv) {
+  int result = -1;
+
+  try {
+    testing::InitGoogleTest(&argc, argv);
+  } catch (...) {
+    std::cerr << "Error duing IniGoogleTest" << std::endl;
+    return 0;
+  }
+
+  try {
+    result = RUN_ALL_TESTS();
+  } catch (...) {
+    std::cerr << "Error duing RUN_ALL_TSETS()" << std::endl;
+  }
+
+  return result;
+}