[API] Training API with generator
authorjijoong.moon <jijoong.moon@samsung.com>
Tue, 19 May 2020 08:09:43 +0000 (17:09 +0900)
committerJijoong Moon <jijoong.moon@samsung.com>
Fri, 29 May 2020 11:43:57 +0000 (20:43 +0900)
There is no API to get training, validation, test data from generator
function.

In this PR, introduces new training API with generator function.
. New Tizen API.
. Add Unit Test API with generator function.
. Add Test generator for future test.

**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>
16 files changed:
Applications/Classification/jni/Android.mk
Applications/KNN/jni/Android.mk
Applications/LogisticRegression/jni/Android.mk
Applications/ReinforcementLearning/DeepQ/jni/Android.mk
Applications/Training/jni/Android.mk
api/capi/include/nntrainer.h
api/capi/src/nntrainer.cpp
meson.build
nntrainer/include/databuffer.h
nntrainer/src/databuffer.cpp
nntrainer/src/databuffer_file.cpp
nntrainer/src/databuffer_func.cpp
nntrainer/src/neuralnet.cpp
test/include/nntrainer_test_util.h
test/nntrainer_test_util.cpp
test/tizen_capi/unittest_tizen_capi.cpp

index 3e22105..7336c06 100644 (file)
@@ -8,7 +8,7 @@ $(error ANDROID_NDK is not defined!)
 endif
 
 ifndef NNTRAINER_ROOT
-NNTRAINER_ROOT := $(LOCAL_PATH)/../../../jni/libs/arm64-v8a
+NNTRAINER_ROOT := $(LOCAL_PATH)/../../../libs/arm64-v8a
 NNTRAINER_INCLUDES := $(LOCAL_PATH)/../../../nntrainer/include/
 endif
 
index 1808a74..8296658 100644 (file)
@@ -8,7 +8,7 @@ $(error ANDROID_NDK is not defined!)
 endif
 
 ifndef NNTRAINER_ROOT
-NNTRAINER_ROOT := $(LOCAL_PATH)/../../../jni/libs/arm64-v8a
+NNTRAINER_ROOT := $(LOCAL_PATH)/../../../libs/arm64-v8a
 NNTRAINER_INCLUDES := $(LOCAL_PATH)/../../../nntrainer/include
 endif
 
index 0e2dd37..c918f6c 100644 (file)
@@ -8,7 +8,7 @@ $(error ANDROID_NDK is not defined!)
 endif
 
 ifndef NNTRAINER_ROOT
-NNTRAINER_ROOT := $(LOCAL_PATH)/../../../jni/libs/arm64-v8a
+NNTRAINER_ROOT := $(LOCAL_PATH)/../../../libs/arm64-v8a
 NNTRAINER_INCLUDE := $(LOCAL_PATH)/../../../nntrainer/include
 endif
 
index 7539cbf..656152b 100644 (file)
@@ -8,7 +8,7 @@ $(error ANDROID_NDK is not defined!)
 endif
 
 ifndef NNTRAINER_ROOT
-NNTRAINER_ROOT := $(LOCAL_PATH)/../../../../jni/libs/arm64-v8a
+NNTRAINER_ROOT := $(LOCAL_PATH)/../../../../libs/arm64-v8a
 NNTRAINER_INCLUDES := $(LOCAL_PATH)/../../../../nntrainer/include
 endif
 
index 83caeeb..3f4347e 100644 (file)
@@ -8,7 +8,7 @@ $(error ANDROID_NDK is not defined!)
 endif
 
 ifndef NNTRAINER_ROOT
-NNTRAINER_ROOT := $(LOCAL_PATH)/../../../jni/libs/arm64-v8a
+NNTRAINER_ROOT := $(LOCAL_PATH)/../../../libs/arm64-v8a
 NNTRAINER_INCLUDES := $(LOCAL_PATH)/../../../nntrainer/include
 endif
 
index 274fc4d..a8a9afa 100644 (file)
@@ -143,6 +143,25 @@ int ml_nnmodel_compile(ml_nnmodel_h model, ml_nnopt_h optimizer, ...);
 int ml_nnmodel_train_with_file(ml_nnmodel_h model, ...);
 
 /**
+ * @brief train the neural network model.
+ * @details Use this function to train neural network model
+ * @since_tizen 6.x
+ * @param[in] model The NNTrainer model handler from the given description.
+ * @param[in] train_func function pointer for train
+ * @param[in] val_func function pointer for val
+ * @param[in] test_func function pointer for test
+ * @param[in] ...  hyper parmeter for train model
+ * @return @c 0 on success. Otherwise a negative error value.
+ * @retval #ML_ERROR_NONE Successful.
+ * @retval #ML_ERROR_INVALID_PARAMETER Invalid Parameter.
+ */
+int ml_nnmodel_train_with_generator(ml_nnmodel_h model,
+                                    bool (*train_func)(float *, float *, int *),
+                                    bool (*val_func)(float *, float *, int *),
+                                    bool (*test_func)(float *, float *, int *),
+                                    ...);
+
+/**
  * @brief Destructs the neural network model.
  * @details Use this function to delete Neural Netowrk Model.
  * @since_tizen 6.x
index edef710..563f6ae 100644 (file)
@@ -148,6 +148,32 @@ int ml_nnmodel_train_with_file(ml_nnmodel_h model, ...) {
   return status;
 }
 
+int ml_nnmodel_train_with_generator(ml_nnmodel_h model,
+                                    bool (*train_func)(float *, float *, int *),
+                                    bool (*val_func)(float *, float *, int *),
+                                    bool (*test_func)(float *, float *, int *),
+                                    ...) {
+  int status = ML_ERROR_NONE;
+  ml_nnmodel *nnmodel;
+  const char *data;
+
+  std::vector<std::string> arg_list;
+  va_list arguments;
+  va_start(arguments, (test_func));
+  while ((data = va_arg(arguments, const char *))) {
+    arg_list.push_back(data);
+  }
+
+  va_end(arguments);
+
+  ML_NNTRAINER_CHECK_MODEL_VALIDATION(nnmodel, model);
+  std::shared_ptr<nntrainer::NeuralNetwork> NN;
+  NN = nnmodel->network;
+
+  status = NN->train((train_func), (val_func), (test_func), arg_list);
+  return status;
+}
+
 int ml_nnmodel_destruct(ml_nnmodel_h model) {
   int status = ML_ERROR_NONE;
   ml_nnmodel *nnmodel;
@@ -157,6 +183,7 @@ int ml_nnmodel_destruct(ml_nnmodel_h model) {
   std::shared_ptr<nntrainer::NeuralNetwork> NN;
   NN = nnmodel->network;
   NN->finalize();
+
   delete nnmodel;
 
   return status;
index 724c543..edf7fc4 100644 (file)
@@ -30,7 +30,8 @@ warning_flags = [
   '-Waddress',
   '-Wno-multichar',
   '-Wvla',
-  '-Wpointer-arith'
+  '-Wpointer-arith',
+  '-Wno-error=varargs'
 ]
 
 warning_c_flags = [
@@ -40,7 +41,8 @@ warning_c_flags = [
   '-Wnested-externs',
   '-Waggregate-return',
   '-Wold-style-definition',
-  '-Wdeclaration-after-statement'
+  '-Wdeclaration-after-statement',
+  '-Wno-error=varargs'  
 ]
 
 foreach extra_arg : warning_flags
index c304f3b..132f0a0 100644 (file)
@@ -73,7 +73,8 @@ typedef std::vector<std::vector<std::vector<float>>> vec_3d;
 typedef enum {
   DATA_NOT_READY = 0,
   DATA_READY = 1,
-  DATA_ERROR = 2,
+  DATA_END = 2,
+  DATA_ERROR = 3,
 } DataStatus;
 
 namespace nntrainer {
@@ -180,14 +181,6 @@ public:
   virtual int clear();
 
   /**
-   * @brief     get Status of Buffer. if number of rest data
-   *            is samller than minibatch, the return false
-   * @param[in] BufferType training, validation, test
-   * @retval    DataStatus
-   */
-  DataStatus getStatus(BufferType type);
-
-  /**
    * @brief     get Data from Data Buffer using databuffer param
    * @param[in] BufferType training, validation, test
    * @param[in] outVec feature data ( minibatch size )
@@ -264,6 +257,13 @@ public:
    */
   bool *getValidation() { return validation; }
 
+  /**
+   * @brief     status of thread
+   */
+  DataStatus trainReadyFlag;
+  DataStatus valReadyFlag;
+  DataStatus testReadyFlag;
+
 protected:
   /**
    * @brief     Data Queues for each data set
@@ -278,7 +278,6 @@ protected:
   /**
    * @brief     feature size
    */
-  /* unsigned int input_size; */
   TensorDim input_dim;
 
   /**
index 25e0889..5e316b7 100644 (file)
@@ -51,10 +51,6 @@ std::condition_variable cv_train;
 std::condition_variable cv_val;
 std::condition_variable cv_test;
 
-DataStatus trainReadyFlag;
-DataStatus valReadyFlag;
-DataStatus testReadyFlag;
-
 int DataBuffer::rangeRandom(int min, int max) {
   int n = max - min + 1;
   int remainder = RAND_MAX % n;
@@ -188,32 +184,6 @@ int DataBuffer::clear() {
   return status;
 }
 
-DataStatus DataBuffer::getStatus(BufferType type) {
-  DataStatus ret = DATA_READY;
-  if (globalExceptionPtr)
-    ret = DATA_ERROR;
-
-  switch (type) {
-  case BUF_TRAIN:
-    if ((train_data.size() < mini_batch) && trainReadyFlag)
-      ret = DATA_NOT_READY;
-    break;
-  case BUF_VAL:
-    if ((val_data.size() < mini_batch) && valReadyFlag)
-      ret = DATA_NOT_READY;
-    break;
-  case BUF_TEST:
-    if ((test_data.size() < mini_batch) && testReadyFlag)
-      ret = DATA_NOT_READY;
-    break;
-  default:
-    ml_loge("Error: Not Supported Data Type");
-    ret = DATA_ERROR;
-    break;
-  }
-  return ret;
-}
-
 bool DataBuffer::getDataFromBuffer(BufferType type, vec_3d &outVec,
                                    vec_3d &outLabel) {
   int nomI;
@@ -224,22 +194,15 @@ bool DataBuffer::getDataFromBuffer(BufferType type, vec_3d &outVec,
   switch (type) {
   case BUF_TRAIN: {
     std::vector<int> list;
+    std::unique_lock<std::mutex> ultrain(readyTrainData);
+    cv_train.wait(ultrain, [this]() -> int { return trainReadyFlag; });
 
-    if (getStatus(BUF_TRAIN) != DATA_READY)
-      return false;
-
-    {
-      std::unique_lock<std::mutex> ultrain(readyTrainData);
-      cv_train.wait(ultrain, []() -> int { return trainReadyFlag; });
-    }
-
-    if (trainReadyFlag == DATA_ERROR) {
+    if (trainReadyFlag == DATA_ERROR || trainReadyFlag == DATA_END) {
       return false;
     }
 
-    data_lock.lock();
     for (k = 0; k < mini_batch; ++k) {
-      nomI = rangeRandom(0, train_data.size() - 1);
+      nomI = rangeRandom(0, train_bufsize - 1);
       std::vector<std::vector<float>> v_height;
       for (j = 0; j < height; ++j) {
         J = j * width;
@@ -254,6 +217,9 @@ bool DataBuffer::getDataFromBuffer(BufferType type, vec_3d &outVec,
       outVec.push_back(v_height);
       outLabel.push_back({train_data_label[nomI]});
     }
+
+    data_lock.lock();
+
     for (i = 0; i < mini_batch; ++i) {
       train_data.erase(train_data.begin() + list[i]);
       train_data_label.erase(train_data_label.begin() + list[i]);
@@ -262,17 +228,14 @@ bool DataBuffer::getDataFromBuffer(BufferType type, vec_3d &outVec,
   } break;
   case BUF_VAL: {
     std::vector<int> list;
-    if (getStatus(BUF_VAL) != DATA_READY)
+    std::unique_lock<std::mutex> ulval(readyValData);
+    cv_val.wait(ulval, [this]() -> bool { return valReadyFlag; });
+    if (valReadyFlag == DATA_ERROR || valReadyFlag == DATA_END) {
       return false;
-
-    {
-      std::unique_lock<std::mutex> ulval(readyValData);
-      cv_val.wait(ulval, []() -> bool { return valReadyFlag; });
     }
 
-    data_lock.lock();
     for (k = 0; k < mini_batch; ++k) {
-      nomI = rangeRandom(0, val_data.size() - 1);
+      nomI = rangeRandom(0, val_bufsize - 1);
       std::vector<std::vector<float>> v_height;
       for (j = 0; j < height; ++j) {
         J = j * width;
@@ -287,25 +250,27 @@ bool DataBuffer::getDataFromBuffer(BufferType type, vec_3d &outVec,
       outVec.push_back(v_height);
       outLabel.push_back({val_data_label[nomI]});
     }
+
+    data_lock.lock();
+
     for (i = 0; i < mini_batch; ++i) {
       val_data.erase(val_data.begin() + list[i]);
       val_data_label.erase(val_data_label.begin() + list[i]);
       cur_val_bufsize--;
     }
+
   } break;
   case BUF_TEST: {
     std::vector<int> list;
-    if (getStatus(BUF_TEST) != DATA_READY)
-      return false;
+    std::unique_lock<std::mutex> ultest(readyTestData);
+    cv_test.wait(ultest, [this]() -> bool { return testReadyFlag; });
 
-    {
-      std::unique_lock<std::mutex> ultest(readyTestData);
-      cv_test.wait(ultest, []() -> bool { return testReadyFlag; });
+    if (testReadyFlag == DATA_ERROR || testReadyFlag == DATA_END) {
+      return false;
     }
 
-    data_lock.lock();
     for (k = 0; k < mini_batch; ++k) {
-      nomI = rangeRandom(0, test_data.size() - 1);
+      nomI = rangeRandom(0, test_bufsize - 1);
       std::vector<std::vector<float>> v_height;
       for (j = 0; j < height; ++j) {
         J = j * width;
@@ -320,6 +285,8 @@ bool DataBuffer::getDataFromBuffer(BufferType type, vec_3d &outVec,
       outVec.push_back(v_height);
       outLabel.push_back({test_data_label[nomI]});
     }
+
+    data_lock.lock();
     for (i = 0; i < mini_batch; ++i) {
       test_data.erase(test_data.begin() + list[i]);
       test_data_label.erase(test_data_label.begin() + list[i]);
index 6c1ef54..c14c03b 100644 (file)
@@ -115,20 +115,20 @@ int DataBufferFromDataFile::init() {
   if (validation[DATA_TRAIN] && max_train < train_bufsize) {
     ml_logw("Warning: Total number of train is less than train buffer size. "
             "Train buffer size is set as total number of train");
-    train_bufsize = max_train;
+    train_bufsize = mini_batch;
   }
 
   if (validation[DATA_VAL] && max_val < val_bufsize) {
     ml_logw(
       "Warning: Total number of validation is less than validation buffer "
       "size. Validation buffer size is set as total number of validation");
-    val_bufsize = max_val;
+    val_bufsize = mini_batch;
   }
 
   if (validation[DATA_TEST] && max_test < test_bufsize) {
     ml_logw("Warning: Total number of test is less than test buffer size. Test "
             "buffer size is set as total number of test");
-    test_bufsize = max_test;
+    test_bufsize = mini_batch;
   }
 
   return status;
@@ -201,7 +201,15 @@ void DataBufferFromDataFile::updateData(BufferType type, int &status) {
   for (unsigned int i = 0; i < max_size; ++i) {
     mark[i] = i;
   }
-  while ((*running) && mark.size() != 0) {
+
+  while ((*running)) {
+    if (mark.size() == 0) {
+      NN_EXCEPTION_NOTI(DATA_END);
+      break;
+    }
+    trainReadyFlag = DATA_NOT_READY;
+    valReadyFlag = DATA_NOT_READY;
+    testReadyFlag = DATA_NOT_READY;
     if (buf_size - (*cur_size) > 0 && (*rest_size) > 0) {
       std::vector<float> vec;
       std::vector<float> veclabel;
@@ -259,11 +267,11 @@ void DataBufferFromDataFile::updateData(BufferType type, int &status) {
       (*cur_size)++;
       data_lock.unlock();
     }
-
     if (buf_size == (*cur_size)) {
       NN_EXCEPTION_NOTI(DATA_READY);
     }
   }
+
   file.close();
 }
 
index 898b974..292498b 100644 (file)
@@ -84,6 +84,18 @@ int DataBufferFromCallback::init() {
     return ML_ERROR_INVALID_PARAMETER;
   }
 
+  if (train_bufsize > mini_batch) {
+    train_bufsize = mini_batch;
+  }
+
+  if (val_bufsize > mini_batch) {
+    val_bufsize = mini_batch;
+  }
+
+  if (test_bufsize > mini_batch) {
+    test_bufsize = mini_batch;
+  }
+
   this->train_running = true;
   this->val_running = true;
   this->test_running = true;
@@ -171,6 +183,7 @@ void DataBufferFromCallback::updateData(BufferType type, int &status) {
     NN_EXCEPTION_NOTI(DATA_ERROR);
     return;
   }
+  bool endflag = false;
 
   float *vec =
     (float *)malloc(sizeof(float) * input_dim.batch() * input_dim.channel() *
@@ -179,54 +192,84 @@ void DataBufferFromCallback::updateData(BufferType type, int &status) {
     (float *)malloc(sizeof(float) * input_dim.batch() * class_num);
 
   while ((*running)) {
+    trainReadyFlag = DATA_NOT_READY;
+    valReadyFlag = DATA_NOT_READY;
+    testReadyFlag = DATA_NOT_READY;
     if (buf_size - (*cur_size) > 0) {
-      bool endflag = callback(vec, veclabel, &status);
-      if (!endflag)
-        break;
+      endflag = callback(vec, veclabel, &status);
 
-      for (unsigned int i = 0; i < input_dim.batch(); ++i) {
-        std::vector<float> v;
-        std::vector<float> vl;
-        unsigned int I =
-          i * input_dim.channel() * input_dim.height() * input_dim.width();
-        for (unsigned int j = 0; j < input_dim.channel(); ++j) {
-          unsigned int J = j * input_dim.height() * input_dim.width();
-          for (unsigned int k = 0; k < input_dim.height() * input_dim.width();
-               ++k) {
-            unsigned int K = I + J + k;
-            v.push_back(vec[K]);
+      if (endflag) {
+        for (unsigned int i = 0; i < input_dim.batch(); ++i) {
+          std::vector<float> v;
+          std::vector<float> vl;
+          unsigned int I =
+            i * input_dim.channel() * input_dim.height() * input_dim.width();
+          for (unsigned int j = 0; j < input_dim.channel(); ++j) {
+            unsigned int J = j * input_dim.height() * input_dim.width();
+            for (unsigned int k = 0; k < input_dim.height() * input_dim.width();
+                 ++k) {
+              unsigned int K = I + J + k;
+              v.push_back(vec[K]);
+            }
           }
-        }
 
-        I = i * class_num;
-        for (unsigned int j = 0; j < class_num; ++j) {
-          vl.push_back(veclabel[I + j]);
-        }
+          I = i * class_num;
+          for (unsigned int j = 0; j < class_num; ++j) {
+            vl.push_back(veclabel[I + j]);
+          }
 
-        data_lock.lock();
-        data->push_back(v);
-        datalabel->push_back(vl);
-        (*cur_size)++;
-        data_lock.unlock();
+          data_lock.lock();
+          data->push_back(v);
+          datalabel->push_back(vl);
+          (*cur_size)++;
+          data_lock.unlock();
+        }
       }
     }
 
-    if (buf_size == (*cur_size)) {
+    if (buf_size == (*cur_size) || !endflag) {
       switch (type) {
       case BUF_TRAIN: {
         std::lock_guard<std::mutex> lgtrain(readyTrainData);
-        trainReadyFlag = DATA_READY;
-        cv_train.notify_all();
+        if (!endflag) {
+          trainReadyFlag = DATA_END;
+          cv_train.notify_all();
+          free(vec);
+          free(veclabel);
+          return;
+        } else {
+          trainReadyFlag = DATA_READY;
+          cv_train.notify_all();
+        }
+
       } break;
       case BUF_VAL: {
         std::lock_guard<std::mutex> lgval(readyValData);
-        valReadyFlag = DATA_READY;
-        cv_val.notify_all();
+        if (!endflag) {
+          valReadyFlag = DATA_END;
+          cv_train.notify_all();
+          free(vec);
+          free(veclabel);
+          return;
+        } else {
+          valReadyFlag = DATA_READY;
+          cv_val.notify_all();
+        }
+
       } break;
       case BUF_TEST: {
         std::lock_guard<std::mutex> lgtest(readyTestData);
-        testReadyFlag = DATA_READY;
-        cv_test.notify_all();
+        if (!endflag) {
+          testReadyFlag = DATA_END;
+          cv_test.notify_all();
+          free(vec);
+          free(veclabel);
+          return;
+        } else {
+          testReadyFlag = DATA_READY;
+          cv_test.notify_all();
+        }
+
       } break;
       default:
         break;
index b3de54d..bd14a6c 100644 (file)
@@ -698,14 +698,6 @@ int NeuralNetwork::train(
 int NeuralNetwork::train_run() {
   int status = ML_ERROR_NONE;
 
-  if (data_buffer->getValidation()[2]) {
-    status = data_buffer->run(nntrainer::BUF_TEST);
-    if (status != ML_ERROR_NONE) {
-      data_buffer->clear(BUF_TEST);
-      return status;
-    }
-  }
-
   float training_loss = 0.0;
   for (unsigned int i = 0; i < epoch; ++i) {
     int count = 0;
@@ -716,6 +708,14 @@ int NeuralNetwork::train_run() {
       return status;
     }
 
+    if (data_buffer->getValidation()[nntrainer::BUF_TEST]) {
+      status = data_buffer->run(nntrainer::BUF_TEST);
+      if (status != ML_ERROR_NONE) {
+        data_buffer->clear(BUF_TEST);
+        return status;
+      }
+    }
+
     while (true) {
       vec_3d in, label;
       if (data_buffer->getDataFromBuffer(nntrainer::BUF_TRAIN, in, label)) {
@@ -733,7 +733,7 @@ int NeuralNetwork::train_run() {
     std::cout << "#" << i + 1 << "/" << epoch
               << " - Training Loss: " << training_loss;
 
-    if (data_buffer->getValidation()[1]) {
+    if (data_buffer->getValidation()[nntrainer::BUF_VAL]) {
       int right = 0;
       float valloss = 0.0;
       int tcases = 0;
index 4e64cbb..76ce5b2 100644 (file)
@@ -142,5 +142,24 @@ const std::string config_str = "[Network]"
  */
 void replaceString(const std::string &from, const std::string &to,
                    const std::string n);
+
+/**
+ * @brief      get data which size is mini batch for train
+ * @param[out] outVec
+ * @param[out] outLabel
+ * @param[out] status for error handling
+ * @retval true/false
+ */
+bool getMiniBatch_train(float *outVec, float *outLabel, int *status);
+
+/**
+ * @brief      get data which size is mini batch for val
+ * @param[out] outVec
+ * @param[out] outLabel
+ * @param[out] status for error handling
+ * @retval true/false
+ */
+bool getMiniBatch_val(float *outVec, float *outLabel, int *status);
+
 #endif /* __cplusplus */
 #endif /* __NNTRAINER_TEST_UTIL_H__ */
index bd2915d..b497fcc 100644 (file)
  */
 
 #include "nntrainer_test_util.h"
+#include <climits>
 #include <iostream>
 
+#define num_class 10
+#define mini_batch 16
+#define feature_size 62720
+
+static bool *duplicate;
+static bool *valduplicate;
+static bool alloc_train = false;
+static bool alloc_val = false;
+
 /**
  * @brief replace string and save it in file
  */
@@ -48,3 +58,191 @@ void replaceString(const std::string &from, const std::string &to,
   data_file << s;
   data_file.close();
 }
+
+/**
+ * @brief     Generate Random integer value between min to max
+ * @param[in] min : minimum value
+ * @param[in] max : maximum value
+ * @retval    min < random value < max
+ */
+static int rangeRandom(int min, int max) {
+  int n = max - min + 1;
+  int remainder = RAND_MAX % n;
+  int x;
+  do {
+    x = rand();
+  } while (x >= RAND_MAX - remainder);
+  return min + x % n;
+}
+
+/**
+ * @brief     load data at specific position of file
+ * @param[in] F  ifstream (input file)
+ * @param[out] outVec
+ * @param[out] outLabel
+ * @param[in] id th data to get
+ * @retval true/false false : end of data
+ */
+static bool getData(std::ifstream &F, std::vector<float> &outVec,
+                    std::vector<float> &outLabel, int id) {
+  F.clear();
+  F.seekg(0, std::ios_base::end);
+  uint64_t file_length = F.tellg();
+  if (id < 0)
+    return false;
+  uint64_t position = (feature_size + num_class) * id * sizeof(float);
+
+  if (position > file_length || position > ULLONG_MAX) {
+    return false;
+  }
+  F.seekg(position, std::ios::beg);
+  for (unsigned int i = 0; i < feature_size; i++)
+    F.read((char *)&outVec[i], sizeof(float));
+  for (unsigned int i = 0; i < num_class; i++)
+    F.read((char *)&outLabel[i], sizeof(float));
+
+  return true;
+}
+
+/**
+ * @brief      get data which size is mini batch for train
+ * @param[out] outVec
+ * @param[out] outLabel
+ * @param[out] status for error handling
+ * @retval true/false
+ */
+bool getMiniBatch_train(float *outVec, float *outLabel, int *status) {
+  std::vector<int> memI;
+  std::vector<int> memJ;
+  unsigned int count = 0;
+  unsigned int data_size = 0;
+
+  std::string filename = "trainingSet.dat";
+  std::ifstream F(filename, std::ios::in | std::ios::binary);
+
+  if (F.good()) {
+    F.seekg(0, std::ios::end);
+    long file_size = F.tellg();
+    data_size = static_cast<unsigned int>(
+      file_size / ((num_class + feature_size) * sizeof(float)));
+  }
+
+  if (!alloc_train) {
+    duplicate = (bool *)malloc(sizeof(bool) * data_size);
+    for (unsigned int i = 0; i < data_size; ++i) {
+      duplicate[i] = false;
+    }
+    alloc_train = true;
+  }
+
+  for (unsigned int i = 0; i < data_size; i++) {
+    if (!duplicate[i])
+      count++;
+  }
+
+  if (count < mini_batch) {
+    free(duplicate);
+    alloc_train = false;
+    return false;
+  }
+
+  count = 0;
+  while (count < mini_batch) {
+    int nomI = rangeRandom(0, data_size - 1);
+    if (!duplicate[nomI]) {
+      memI.push_back(nomI);
+      duplicate[nomI] = true;
+      count++;
+    }
+  }
+
+  for (unsigned int i = 0; i < count; i++) {
+    std::vector<float> o;
+    std::vector<float> l;
+
+    o.resize(feature_size);
+    l.resize(num_class);
+
+    getData(F, o, l, memI[i]);
+
+    for (unsigned int j = 0; j < feature_size; ++j)
+      outVec[i * feature_size + j] = o[j];
+    for (unsigned int j = 0; j < num_class; ++j)
+      outLabel[i * num_class + j] = l[j];
+  }
+
+  F.close();
+  return true;
+}
+
+/**
+ * @brief      get data which size is mini batch for validation
+ * @param[out] outVec
+ * @param[out] outLabel
+ * @param[out] status for error handling
+ * @retval true/false false : end of data
+ */
+bool getMiniBatch_val(float *outVec, float *outLabel, int *status) {
+
+  std::vector<int> memI;
+  std::vector<int> memJ;
+  unsigned int count = 0;
+  unsigned int data_size = 0;
+
+  std::string filename = "trainingSet.dat";
+  std::ifstream F(filename, std::ios::in | std::ios::binary);
+
+  if (F.good()) {
+    F.seekg(0, std::ios::end);
+    long file_size = F.tellg();
+    data_size = static_cast<unsigned int>(
+      file_size / ((num_class + feature_size) * sizeof(float)));
+  }
+
+  if (!alloc_val) {
+    valduplicate = (bool *)malloc(sizeof(bool) * data_size);
+    for (unsigned int i = 0; i < data_size; ++i) {
+      valduplicate[i] = false;
+    }
+    alloc_val = true;
+  }
+
+  for (unsigned int i = 0; i < data_size; i++) {
+    if (!valduplicate[i])
+      count++;
+  }
+
+  if (count < mini_batch) {
+    free(valduplicate);
+    alloc_val = false;
+    return false;
+  }
+
+  count = 0;
+  while (count < mini_batch) {
+    int nomI = rangeRandom(0, data_size - 1);
+    if (!valduplicate[nomI]) {
+      memI.push_back(nomI);
+      valduplicate[nomI] = true;
+      count++;
+    }
+  }
+
+  for (unsigned int i = 0; i < count; i++) {
+    std::vector<float> o;
+    std::vector<float> l;
+
+    o.resize(feature_size);
+    l.resize(num_class);
+
+    getData(F, o, l, memI[i]);
+
+    for (unsigned int j = 0; j < feature_size; ++j)
+      outVec[i * feature_size + j] = o[j];
+    for (unsigned int j = 0; j < num_class; ++j)
+      outLabel[i * num_class + j] = l[j];
+  }
+
+  F.close();
+  return true;
+}
index 98f0113..b0d524a 100644 (file)
@@ -441,6 +441,67 @@ TEST(nntrainer_capi_nnmodel, train_with_file_01_p) {
   EXPECT_EQ(status, ML_ERROR_NONE);
   status = ml_nnoptimizer_delete(optimizer);
   EXPECT_EQ(status, ML_ERROR_NONE);
+  status = ml_nnmodel_destruct(model);
+  EXPECT_EQ(status, ML_ERROR_NONE);
+}
+
+/**
+ * @brief Neural Network Model Optimizer Test
+ */
+TEST(nntrainer_capi_nnmodel, train_with_generator_01_p) {
+  int status = ML_ERROR_NONE;
+
+  ml_nnmodel_h model;
+  ml_nnlayer_h layers[2];
+  ml_nnopt_h optimizer;
+
+  status = ml_nnmodel_construct(&model);
+  EXPECT_EQ(status, ML_ERROR_NONE);
+
+  status = ml_nnlayer_create(&layers[0], ML_LAYER_TYPE_INPUT);
+  EXPECT_EQ(status, ML_ERROR_NONE);
+
+  status =
+    ml_nnlayer_set_property(layers[0], "input_shape= 16:1:1:62720",
+                            "normalization=true", "bias_zero=true", NULL);
+  EXPECT_EQ(status, ML_ERROR_NONE);
+
+  status = ml_nnmodel_add_layer(model, layers[0]);
+  EXPECT_EQ(status, ML_ERROR_NONE);
+
+  status = ml_nnlayer_create(&layers[1], ML_LAYER_TYPE_FC);
+  EXPECT_EQ(status, ML_ERROR_NONE);
+
+  status = ml_nnlayer_set_property(layers[1], "unit= 10", "activation=softmax",
+                                   "bias_zero=true", "weight_decay=l2norm",
+                                   "weight_decay_lambda=0.005",
+                                   "weight_ini=xavier_uniform", NULL);
+  EXPECT_EQ(status, ML_ERROR_NONE);
+
+  status = ml_nnmodel_add_layer(model, layers[1]);
+  EXPECT_EQ(status, ML_ERROR_NONE);
+
+  status = ml_nnoptimizer_create(&optimizer, "adam");
+  EXPECT_EQ(status, ML_ERROR_NONE);
+
+  status = ml_nnoptimizer_set_property(
+    optimizer, "learning_rate=0.0001", "decay_rate=0.96", "decay_steps=1000",
+    "beta1=0.002", "beta2=0.001", "epsilon=1e-7", NULL);
+  EXPECT_EQ(status, ML_ERROR_NONE);
+
+  status = ml_nnmodel_compile(model, optimizer, "loss=cross", NULL);
+  EXPECT_EQ(status, ML_ERROR_NONE);
+  status = ml_nnmodel_train_with_generator(
+    model, getMiniBatch_train, getMiniBatch_val, NULL, "epochs=1",
+    "batch_size=16", "buffer_size=100", "model_file=model.bin", NULL);
+
+  EXPECT_EQ(status, ML_ERROR_NONE);
+  status = ml_nnlayer_delete(layers[0]);
+  EXPECT_EQ(status, ML_ERROR_NONE);
+  status = ml_nnlayer_delete(layers[1]);
+  EXPECT_EQ(status, ML_ERROR_NONE);
+  status = ml_nnoptimizer_delete(optimizer);
+  EXPECT_EQ(status, ML_ERROR_NONE);
 
   status = ml_nnmodel_destruct(model);
   EXPECT_EQ(status, ML_ERROR_NONE);