[Optimizer] Refactor to use props
authorJihoon Lee <jhoon.it.lee@samsung.com>
Wed, 1 Sep 2021 06:11:34 +0000 (15:11 +0900)
committerJijoong Moon <jijoong.moon@samsung.com>
Tue, 7 Sep 2021 11:27:12 +0000 (20:27 +0900)
This patch refactors optimizer families to use properties while removing
some unused functions and constructors

**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>
12 files changed:
nntrainer/models/model_loader.cpp
nntrainer/optimizers/adam.cpp
nntrainer/optimizers/adam.h
nntrainer/optimizers/optimizer_devel.h
nntrainer/optimizers/optimizer_impl.cpp
nntrainer/optimizers/optimizer_impl.h
nntrainer/optimizers/sgd.cpp
nntrainer/optimizers/sgd.h
nntrainer/utils/base_properties.cpp
nntrainer/utils/base_properties.h
nntrainer/utils/parse_util.cpp
nntrainer/utils/parse_util.h

index abad75f3ce5df63dc65457cdccca00e68bfe8cfd..f9c0d43e1cb119351e22332e95d09847eb77beab 100644 (file)
@@ -146,41 +146,23 @@ int ModelLoader::loadModelConfigIni(dictionary *ini, NeuralNetwork &model) {
   }
 
   std::vector<std::string> optimizer_prop = {};
-  optimizer_prop.push_back(
-    {"learning_rate=" +
-     std::string(iniparser_getstring(
-       ini, "Model:Learning_rate",
-       std::to_string(model.opt->getLearningRate()).c_str()))});
-
-  if (model.opt->getType() == SGD::type || model.opt->getType() == Adam::type) {
-    std::shared_ptr<OptimizerImpl> opt_impl =
-      std::static_pointer_cast<OptimizerImpl>(model.opt);
-
-    optimizer_prop.push_back(
-      {"decay_steps=" + std::string(iniparser_getstring(
-                          ini, "Model:Decay_steps",
-                          std::to_string(opt_impl->getDecaySteps()).c_str()))});
-    optimizer_prop.push_back(
-      {"decay_rate=" + std::string(iniparser_getstring(
-                         ini, "Model:Decay_rate",
-                         std::to_string(opt_impl->getDecayRate()).c_str()))});
-
-    if (opt_impl->getType() == "adam") {
-      std::shared_ptr<Adam> opt_adam = std::static_pointer_cast<Adam>(opt_impl);
-
-      optimizer_prop.push_back(
-        {"beta1=" +
-         std::string(iniparser_getstring(
-           ini, "Model:Beta1", std::to_string(opt_adam->getBeta1()).c_str()))});
-      optimizer_prop.push_back(
-        {"beta2=" +
-         std::string(iniparser_getstring(
-           ini, "Model:Beta2", std::to_string(opt_adam->getBeta2()).c_str()))});
-      optimizer_prop.push_back(
-        {"epsilon=" + std::string(iniparser_getstring(
-                        ini, "Model:Epsilon",
-                        std::to_string(opt_adam->getEpsilon()).c_str()))});
+
+  /** push only if ini_key exist as prop_key=ini_value */
+  auto maybe_push = [ini](std::vector<std::string> &prop_vector,
+                          const std::string &ini_key,
+                          const std::string &prop_key) {
+    constexpr const char *LOCAL_UNKNOWN = "unknown";
+    std::string ini_value =
+      iniparser_getstring(ini, ini_key.c_str(), LOCAL_UNKNOWN);
+    if (!istrequal(ini_value, LOCAL_UNKNOWN)) {
+      prop_vector.push_back(prop_key + "=" + ini_value);
     }
+  };
+
+  const std::vector<std::string> deprecated_optimizer_keys = {
+    "learning_rate", "decay_rate", "decay_steps", "beta1", "beta2", "epsilon"};
+  for (const auto &key : deprecated_optimizer_keys) {
+    maybe_push(optimizer_prop, "Model:" + key, key);
   }
 
   try {
index 47040f50183c7f6aa2bfbd639e50836f62daa9c0..327341243f9ea1ac40a7ffd094788d95e2922434 100644 (file)
 #include <adam.h>
 #include <nntrainer_error.h>
 #include <nntrainer_log.h>
-#include <parse_util.h>
+#include <node_exporter.h>
 #include <util_func.h>
 
 namespace nntrainer {
 
+Adam::Adam() : adam_props(PropsB1(), PropsB2(), PropsEpsilon()) {
+  /** default properties */
+  setProperty({"learning_rate=0.001"});
+  auto &[b1, b2, eps] = adam_props;
+  b1.set(0.9f);
+  b2.set(0.999f);
+  eps.set(1.0e-7f);
+}
+
+Adam::~Adam() {}
+
 enum AdamParams { wm, wv };
 
 std::vector<TensorDim> Adam::getOptimizerVariableDim(const TensorDim &dim) {
   return {dim, dim};
 }
 
+void Adam::exportTo(Exporter &exporter, const ExportMethods &method) const {
+  exporter.saveResult(adam_props, method, this);
+  OptimizerImpl::exportTo(exporter, method);
+}
+
+void Adam::setProperty(const std::vector<std::string> &values) {
+  auto left = loadProperties(values, adam_props);
+  OptimizerImpl::setProperty(left);
+}
+
 double Adam::getLearningRate(size_t iteration) const {
+  auto &beta1 = std::get<PropsB1>(adam_props).get();
+  auto &beta2 = std::get<PropsB2>(adam_props).get();
   double ll = OptimizerImpl::getLearningRate(iteration);
 
   std::function<float(double)> biasCorrection = [&](float f) {
@@ -41,9 +64,12 @@ double Adam::getLearningRate(size_t iteration) const {
 }
 
 void Adam::applyGradient(RunOptimizerContext &context) {
-
   Tensor &x_grad = context.getGradient();
 
+  auto &beta1 = std::get<PropsB1>(adam_props).get();
+  auto &beta2 = std::get<PropsB2>(adam_props).get();
+  auto &epsilon = std::get<PropsEpsilon>(adam_props).get();
+
   // This is implementation of adam from original paper.
   // This is not deleted intentionally.
   // float biasCorrection1 = 1 - pow(beta1, iteration + 1);
@@ -62,8 +88,8 @@ void Adam::applyGradient(RunOptimizerContext &context) {
   //                  .add(epsilon);
   // x.add_i(wm.divide(denom), -ll / biasCorrection1);
 
-  std::function<double(double)> sqrtEps = [&](double f) {
-    return 1 / (sqrtDouble(f) + this->epsilon);
+  std::function<double(double)> sqrtEps = [epsilon](double f) {
+    return 1 / (sqrtDouble(f) + epsilon);
   };
 
   Tensor &wm = context.getOptimizerVariable(AdamParams::wm);
@@ -80,27 +106,4 @@ void Adam::applyGradient(RunOptimizerContext &context) {
   context.applyGradient(getLearningRate(context.getIteration()));
 }
 
-void Adam::setProperty(const std::string &key, const std::string &value) {
-  int status = ML_ERROR_NONE;
-  PropertyType type = static_cast<PropertyType>(parseOptProperty(key));
-
-  switch (type) {
-  case PropertyType::beta1:
-    status = setDouble(beta1, value);
-    break;
-  case PropertyType::beta2:
-    status = setDouble(beta2, value);
-    break;
-  case PropertyType::epsilon:
-    status = setDouble(epsilon, value);
-    break;
-  default:
-    OptimizerImpl::setProperty(key, value);
-    status = ML_ERROR_NONE;
-    break;
-  }
-
-  throw_status(status);
-}
-
 } // namespace nntrainer
index 66c879edc0203568e013f65da42fb9f9a9f47cff..0fff611218fbca39eab44a67bea8e193493262b1 100644 (file)
 #define __ADAM_H__
 #ifdef __cplusplus
 
+#include <tuple>
+
+#include <base_properties.h>
 #include <optimizer_impl.h>
 
 namespace nntrainer {
 
+/**
+ * @brief Beta 1 props
+ *
+ */
+class PropsB1 : public Property<double> {
+public:
+  static constexpr const char *key = "beta1"; /**< unique key to access */
+  using prop_tag = double_prop_tag;           /**< property type */
+};
+
+/**
+ * @brief Beta 2 props
+ *
+ */
+class PropsB2 : public Property<double> {
+public:
+  static constexpr const char *key = "beta2"; /**< unique key to access */
+  using prop_tag = double_prop_tag;           /**< property type */
+};
+
+/**
+ * @brief epsilon props
+ * @todo move this to common props
+ *
+ */
+class PropsEpsilon : public Property<double> {
+public:
+  static constexpr const char *key = "epsilon"; /**< unique key to access */
+  using prop_tag = double_prop_tag;             /**< property type */
+};
+
 /**
  * @class   Adam optimizer class
  * @brief   Adam optimizer
@@ -25,15 +59,16 @@ namespace nntrainer {
 class Adam : public OptimizerImpl {
 public:
   /**
-   * @brief     Constructor of Optimizer Class
+   * @brief Construct a new Adam object
+   *
    */
-  template <typename... Args>
-  Adam(float lr = 0.001f, double b1 = 0.9f, double b2 = 0.999f,
-       double ep = 1.0e-7f, Args... args) :
-    OptimizerImpl(lr, args...),
-    beta1(b1),
-    beta2(b2),
-    epsilon(ep) {}
+  Adam();
+
+  /**
+   * @brief Destroy the Adam object
+   *
+   */
+  ~Adam();
 
   /**
    * @copydoc applyGradient(RunOptimizerContext &context)
@@ -56,32 +91,20 @@ public:
   std::vector<TensorDim> getOptimizerVariableDim(const TensorDim &dim) override;
 
   /**
-   * @brief get beta1
+   * @copydoc Optimizer::exportTo(Exporter &exporter, const ExportMethods&
+   * method)
    */
-  double getBeta1() { return beta1; };
+  void exportTo(Exporter &exporter, const ExportMethods &method) const override;
 
-  /**
-   * @brief get beta2
-   */
-  double getBeta2() { return beta2; };
+  inline static const std::string type = "adam";
 
   /**
-   * @brief get epsilon
+   * @copydoc Optimizer::setProperty(const std::vector<std::string> &values)
    */
-  double getEpsilon() { return epsilon; }
-
-  inline static const std::string type = "adam";
+  virtual void setProperty(const std::vector<std::string> &values) override;
 
 private:
-  double beta1;   /** momentum for grad */
-  double beta2;   /** momentum for grad**2 */
-  double epsilon; /** epsilon to protect overflow */
-
-  /**
-   * @copydoc LayerImpl::setProperty(const std::string &key,
-                           const std::string &value)
-   */
-  void setProperty(const std::string &key, const std::string &value);
+  std::tuple<PropsB1, PropsB2, PropsEpsilon> adam_props;
 };
 } /* namespace nntrainer */
 
index de2acfb35a5a1ae9b647c19d20427e4aa111632f..cc5122715b01bc7242c2b91e63d649785d5ab664 100644 (file)
@@ -24,6 +24,9 @@
 
 namespace nntrainer {
 
+class Exporter;
+enum class ExportMethods;
+
 /**
  * @class   Optimizer Base class for optimizers
  * @brief   Base class for all optimizers
@@ -59,31 +62,14 @@ public:
   virtual void setProperty(const std::vector<std::string> &values);
 
   /**
-   * @brief     Default allowed properties
-   * Available for all optimizers
-   * - learning_rate : float
-   *
-   * Available for SGD and Adam optimizers
-   * - decay_rate : float,
-   * - decay_steps : float,
-   *
-   * Available for Adam optimizer
-   * - beta1 : float,
-   * - beta2 : float,
-   * - epsilon : float,
+   * @brief this function helps exporting the optimizer in a predefined format,
+   * while workarounding issue caused by templated function type eraser
    *
-   * @todo: convert to string
+   * @param     exporter exporter that conatins exporting logic
+   * @param     method enum value to identify how it should be exported to
    */
-  enum class PropertyType {
-    learning_rate = 0,
-    decay_rate = 1,
-    decay_steps = 2,
-    beta1 = 3,
-    beta2 = 4,
-    epsilon = 5,
-    continue_train = 6,
-    unknown = 7,
-  };
+  virtual void exportTo(Exporter &exporter, const ExportMethods &method) const {
+  }
 
   /**
    * @brief     finalize optimizer.
index d929cb7ca9074827e10e3f81145d897312912675..305c9831020153c2299d5292dc946910719c865e 100644 (file)
 #include <cmath>
 #include <nntrainer_error.h>
 #include <nntrainer_log.h>
+#include <node_exporter.h>
 #include <optimizer_impl.h>
-#include <parse_util.h>
 #include <util_func.h>
 
 namespace nntrainer {
 
-void OptimizerImpl::setProperty(const std::vector<std::string> &values) {
-  /// @todo: deprecate this in favor of loadProperties
-  for (unsigned int i = 0; i < values.size(); ++i) {
-    std::string key;
-    std::string value;
-    std::stringstream ss;
-
-    if (getKeyValue(values[i], key, value) != ML_ERROR_NONE) {
-      throw std::invalid_argument("Error parsing the property: " + values[i]);
-    }
-
-    if (value.empty()) {
-      ss << "value is empty: key: " << key << ", value: " << value;
-      throw std::invalid_argument(ss.str());
-    }
+OptimizerImpl::OptimizerImpl() :
+  optimizer_impl_props(PropsLR(), PropsDecayRate(), PropsDecaySteps()) {}
 
-    /// @note this calls derived setProperty if available
-    setProperty(key, value);
-  }
+void OptimizerImpl::setProperty(const std::vector<std::string> &values) {
+  auto left = loadProperties(values, optimizer_impl_props);
+  NNTR_THROW_IF(left.size(), std::invalid_argument)
+    << "[OptimizerImpl] There are unparsed properties";
 }
 
-void OptimizerImpl::setProperty(const std::string &key,
-                                const std::string &value) {
-  int status = ML_ERROR_NONE;
-  PropertyType type = static_cast<PropertyType>(parseOptProperty(key));
-
-  switch (type) {
-  case PropertyType::learning_rate:
-    status = setFloat(learning_rate, value);
-    break;
-  case PropertyType::decay_steps:
-    status = setUint(decay_steps, value);
-    break;
-  case PropertyType::decay_rate:
-    status = setFloat(decay_rate, value);
-    break;
-  case PropertyType::continue_train:
-    status = setBoolean(continue_train, value);
-    break;
-  default:
-    status = ML_ERROR_INVALID_PARAMETER;
-    break;
-  }
-
-  throw_status(status);
+void OptimizerImpl::exportTo(Exporter &exporter,
+                             const ExportMethods &method) const {
+  exporter.saveResult(optimizer_impl_props, method, this);
 }
 
 double OptimizerImpl::getLearningRate(size_t iteration) const {
-  double ll = learning_rate;
 
-  if (decay_steps != 0) {
+  auto &[float_lr, decay_rate, decay_steps] = optimizer_impl_props;
+  double ll = float_lr;
+
+  if (!decay_steps.empty() && !decay_rate.empty()) {
     ll = ll * pow(decay_rate, (iteration / (float)decay_steps));
   }
 
index 96bc2cbaae869c52b7bfbf571f055b7a67b44503..4334ba7ba58d7e90e20455f883ee7de08b443c75 100644 (file)
 #define __OPTIMIZER_IMPL_H__
 #ifdef __cplusplus
 
+#include <tuple>
+
+#include <base_properties.h>
 #include <optimizer_devel.h>
 
 namespace nntrainer {
 
+/**
+ * @brief Learning Rate props
+ *
+ */
+class PropsLR : public Property<float> {
+public:
+  static constexpr const char *key =
+    "learning_rate";               /**< unique key to access */
+  using prop_tag = float_prop_tag; /**< property type */
+};
+
+/**
+ * @brief Decay rate property
+ *
+ */
+class PropsDecayRate : public Property<float> {
+public:
+  static constexpr const char *key = "decay_rate"; /**< unique key to access */
+  using prop_tag = float_prop_tag;                 /**< property type */
+};
+
+/**
+ * @brief decay steps property
+ *
+ */
+class PropsDecaySteps : public PositiveIntegerProperty {
+public:
+  static constexpr const char *key = "decay_steps"; /**< unique key to access */
+  using prop_tag = uint_prop_tag;                   /**< property type */
+};
+
 /**
  * @class   Optimizer Base class for optimizers
  * @brief   Basic implementation class for nntrainer supported optimizers
@@ -28,15 +62,10 @@ class OptimizerImpl : public Optimizer {
 
 public:
   /**
-   * @brief     Default Constructor of Optimizer Class
+   * @brief Construct a new Optimizer Impl object
+   *
    */
-  OptimizerImpl(float lr, float decay_rate = 1.0f, unsigned int decay_steps = 0,
-                float continue_train = false) :
-    Optimizer(),
-    learning_rate(lr),
-    decay_rate(decay_rate),
-    decay_steps(decay_steps),
-    continue_train(continue_train) {}
+  OptimizerImpl();
 
   /**
    * @brief  copy constructor
@@ -60,25 +89,7 @@ public:
    * @brief  Move assignment operator.
    * @parma[in] rhs OptimizerImpl to be moved.
    */
-  OptimizerImpl &operator=(OptimizerImpl &&rhs) = default;
-
-  /**
-   * @brief     get Learning Rate
-   * @retval    Learning rate in float
-   */
-  float getLearningRate() const { return learning_rate; };
-
-  /**
-   * @brief     get Decay Rate for learning rate decay
-   * @retval    decay rate
-   */
-  float getDecayRate() const { return decay_rate; };
-
-  /**
-   * @brief     get Decay Steps for learning rate decay
-   * @retval    decay steps
-   */
-  float getDecaySteps() const { return decay_steps; };
+  OptimizerImpl &operator=(OptimizerImpl &&rhs) noexcept = default;
 
   /**
    * @brief     get Learning Rate for the given iteration
@@ -88,10 +99,16 @@ public:
   double getLearningRate(size_t iteration) const;
 
   /**
-   * @copydoc Layer::setProperty(const std::vector<std::string> &values)
+   * @copydoc Optimizer::setProperty(const std::vector<std::string> &values)
    */
   virtual void setProperty(const std::vector<std::string> &values);
 
+  /**
+   * @copydoc Optimizer::exportTo(Exporter &exporter, const ExportMethods&
+   * method)
+   */
+  void exportTo(Exporter &exporter, const ExportMethods &method) const override;
+
   /**
    * @brief     Get dimension of extra variables if the optimizer needs any.
    * @param dim Dimension of tensor to be added as a optimizer variable
@@ -103,22 +120,7 @@ public:
   }
 
 protected:
-  float learning_rate;      /**< learning rate */
-  float decay_rate;         /** decay rate for learning rate */
-  unsigned int decay_steps; /** decay steps for learning rate */
-  bool continue_train; /** Continue training with previous tensors for adam */
-
-  /**
-   * @brief setProperty individually
-   * @note By passing empty string, this can validate if @a type is valid
-   * @param[in] key key to be passed as string
-   * @param[in] value value to be passed, if empty string is passed, do nothing
-   * but throws error when @a type is invalid
-   * @exception exception::not_supported     when string type is not valid for
-   * the particular layer
-   * @exception std::invalid_argument invalid argument
-   */
-  virtual void setProperty(const std::string &key, const std::string &value);
+  std::tuple<PropsLR, PropsDecayRate, PropsDecaySteps> optimizer_impl_props;
 };
 
 } /* namespace nntrainer */
index e03dc960145136959c1a33e331f98b7210caad13..1225819ab8deef70e9ad3fac1be06d8f45969352 100644 (file)
@@ -15,6 +15,8 @@
 
 namespace nntrainer {
 
+SGD::SGD() { setProperty({"learning_rate=0.0001"}); }
+
 void SGD::applyGradient(RunOptimizerContext &context) {
   context.applyGradient(getLearningRate(context.getIteration()));
 }
index 155c78cae9adf89cbf99e648096422091f7aac33..2a3d8167d0fc65bcfa34be43cd10a82662df53b7 100644 (file)
@@ -25,10 +25,10 @@ namespace nntrainer {
 class SGD : public OptimizerImpl {
 public:
   /**
-   * @brief     Constructor of Optimizer Class
+   * @brief Construct a new SGD object
+   *
    */
-  template <typename... Args>
-  SGD(float lr = 0.0001f, Args... args) : OptimizerImpl(lr, args...) {}
+  SGD();
 
   /**
    * @copydoc applyGradient(RunOptimizerContext &context)
index dc616f3b0de50c280d33b8cd0d9978b42c595f04..c9f679aa5a1a84265490600dd8975bdefb1fc367 100644 (file)
@@ -85,6 +85,18 @@ float str_converter<float_prop_tag, float>::from_string(
   return std::stof(value);
 }
 
+template <>
+std::string
+str_converter<double_prop_tag, double>::to_string(const double &value) {
+  return std::to_string(value);
+}
+
+template <>
+double
+str_converter<double_prop_tag, double>::from_string(const std::string &value) {
+  return std::stod(value);
+}
+
 template <>
 std::string str_converter<dimension_prop_tag, TensorDim>::to_string(
   const TensorDim &dimension) {
index 4c00c328e8a0d4326f8c489c4161f41ee5942f0a..2ddb37f9ee456560966a3f37cd25a3ce542e9201 100644 (file)
@@ -98,6 +98,12 @@ struct dimension_prop_tag {};
  */
 struct float_prop_tag {};
 
+/**
+ * @brief property is treated as double
+ *
+ */
+struct double_prop_tag {};
+
 /**
  * @brief property is treated as string
  *
@@ -427,6 +433,20 @@ template <>
 float str_converter<float_prop_tag, float>::from_string(
   const std::string &value);
 
+/**
+ * @copydoc template <typename Tag, typename DataType> struct str_converter
+ */
+template <>
+std::string
+str_converter<double_prop_tag, double>::to_string(const double &value);
+
+/**
+ * @copydoc template <typename Tag, typename DataType> struct str_converter
+ */
+template <>
+double
+str_converter<double_prop_tag, double>::from_string(const std::string &value);
+
 /**
  * @brief convert dispatcher (to string)
  *
index 454e67d97c5b4a1fbadf60321f5271c2eb7065a8..176e4e064c6f4bc17033c9d38fc18dfc7aaca1e7 100644 (file)
@@ -285,34 +285,6 @@ unsigned int parseLayerProperty(std::string property) {
 
 std::string propToStr(unsigned int type) { return property_string[type]; }
 
-unsigned int parseOptProperty(std::string property) {
-  unsigned int i;
-
-  /**
-   * @brief     Optimizer Properties
-   * learning_rate = 0,
-   * decay_rate = 1,
-   * decay_steps = 2
-   * beta1 = 3,
-   * beta2 = 4,
-   * epsilon = 5,
-   */
-  std::array<std::string, 7> property_string = {
-    "learning_rate", "decay_rate", "decay_steps", "beta1", "beta2", "epsilon"};
-
-  for (i = 0; i < property_string.size(); i++) {
-    unsigned int size = (property_string[i].size() > property.size())
-                          ? property_string[i].size()
-                          : property.size();
-
-    if (!strncasecmp(property_string[i].c_str(), property.c_str(), size)) {
-      return (i);
-    }
-  }
-
-  return (unsigned int)Optimizer::PropertyType::unknown;
-}
-
 int setUint(unsigned int &val, const std::string &str) {
   int status = ML_ERROR_NONE;
   try {
index e517906f7f70334b0951b5bda0662ca99db3bc8a..9dac3a30c54f6a694ec6d6a881a46e85ed19ca67 100644 (file)
@@ -76,13 +76,6 @@ std::string propToStr(const unsigned int type);
  */
 unsigned int parseType(std::string ll, InputType t);
 
-/**
- * @brief     Parsing Optimizer Property
- * @param[in] property string to be parsed
- * @retval    int enumerated type
- */
-unsigned int parseOptProperty(std::string property);
-
 } /* namespace nntrainer */
 
 #endif /* __cplusplus */