[Example] Add display result accepted/tizen/unified/20200820.034726 submit/tizen/20200819.035140
authorJihoon Lee <jhoon.it.lee@samsung.com>
Thu, 13 Aug 2020 02:34:26 +0000 (11:34 +0900)
committerJijoong Moon <jijoong.moon@samsung.com>
Tue, 18 Aug 2020 10:35:00 +0000 (19:35 +0900)
**Changes proposed in this PR:**
- Run training asynchronsly
- Parse train result
- Change draw / result appearance

**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>
Applications/Tizen_native/CustomShortcut/inc/data.h
Applications/Tizen_native/CustomShortcut/inc/view.h
Applications/Tizen_native/CustomShortcut/res/edje/main.edc
Applications/Tizen_native/CustomShortcut/src/data.c
Applications/Tizen_native/CustomShortcut/src/main.c
Applications/Tizen_native/CustomShortcut/src/view.c

index a1bdb1c..d37c1ab 100644 (file)
@@ -30,9 +30,9 @@
 
 #define EDJ_PATH "edje/main.edj"
 #define TRAIN_SET_PATH "trainingSet.dat"
-#define VALIDATION_SET_PATH "validationSet.dat"
+#define VALIDATION_SET_PATH "trainingSet.dat"
 
-#define MAX_TRAIN_TRIES 5
+#define MAX_TRAIN_TRIES 10
 #define MAX_TRIES 10
 
 #define FEATURE_SIZE 62720
@@ -61,14 +61,28 @@ typedef struct appdata {
   cairo_t *cr;                 /**< cairo engine for the canvas */
   int tries;                   /**< tells how many data has been labeled */
 
-  /**< ML related */
+  /**< Feature extraction related */
   ml_pipeline_h pipeline;       /**< handle of feature extractor */
   ml_pipeline_sink_h pipe_sink; /**< sink of pipeline */
   char pipe_dst[PATH_MAX];      /**< destination path where to save */
   pthread_mutex_t pipe_lock; /**< ensures that only one pipe runs at a time */
   pthread_cond_t pipe_cond;  /**< pipe condition to block at a point */
+
+  /**< Training related */
+  pthread_t tid_writer;         /**< thread handler to run trainer */
+  pthread_t tid_reader;         /**< thread handler to read train result */
+  int pipe_fd[2];               /**< fd for pipe */
+  Ecore_Pipe *data_output_pipe; /**< pipe to write information to ui */
+  double best_accuracy;         /**< stores best accuracy */
 } appdata_s;
 
+typedef struct train_result {
+  int epoch;         /**< current epoch */
+  double accuracy;   /**< accuracy of validation result */
+  double train_loss; /**< train loss */
+  double valid_loss; /**< validation loss */
+} train_result_s;
+
 /**
  * @brief separate route path from route
  * @note this function copies @a source, and change ':' to '\0' and return start
@@ -104,9 +118,26 @@ int data_get_resource_path(const char *file, char *full_path, bool shared);
 int data_extract_feature(appdata_s *ad, const char *dst, bool append);
 
 /**
- * @brief nntrainer training model
+ * @brief nntrainer training model that is to run from pthread_create
+ * @param[in] data appdata.
+ * @return not used.
+ */
+void *data_run_model(void *ad);
+
+/**
+ * @brief parse result string
+ * @param[in] result result string to be parsed.
+ * @param[out] train_result structured data from result string
+ * @retval APP_ERROR_NONE if no error
+ * @retval APP_ERROR_INVALID_PARAMETER if string can't be parsed
+ *
+ * result string is like:
+ * #1/10 - Training Loss: 0.717496 >> [ Accuracy: 75% - Validation Loss :
+ 0.667001 ]
+ * #10/10 - Training Loss: 0.398767 >> [ Accuracy: 75% - Validation Loss :
+ 0.467543 ]
  */
-void data_train_model();
+int data_parse_result_string(const char *src, train_result_s *train_result);
 
 #if !defined(PACKAGE)
 #define PACKAGE "org.example.nntrainer-example-custom-shortcut"
index 1befd30..827db54 100644 (file)
@@ -31,4 +31,12 @@ int view_init(appdata_s *ad);
  */
 int view_routes_to(appdata_s *ad, const char *group_name);
 
+/**
+ * @brief callback function to update training result
+ * @param[in] data user data
+ * @param[in] buffer arrays of null terminated characters
+ * @param[in] nbytes max length of the buffer
+ */
+void view_update_result_cb(void *data, void *buffer, unsigned int nbytes);
+
 #endif /* __nntrainer_example_custom_shortcut_view_H__ */
index ac0690e..eba4fc8 100644 (file)
@@ -106,6 +106,20 @@ collections {
     parts {
       PART_TITLE("draw/title", "draw for 😊")
       part {
+        name: "draw/label";
+        type: TEXT;
+        description {
+          state: "default" 0.0;
+          rel1.relative: 0.0 0.2;
+          rel2.relative: 1.0 0.8;
+          text {
+            text: "😊";
+            size: 58;
+            align: 0.5 0.5;
+          }
+        }
+      }
+      part {
         name: "draw/canvas";
         type: SWALLOW;
         description { state: "default" 0.0;
@@ -113,7 +127,7 @@ collections {
           rel2.relative: 1.0 0.8;
         }
       }
-      PART_BUTTON("draw/reset", "↩", 0.2, 0.83, 0.45, 0.9)
+      PART_BUTTON("draw/reset", "↩", 0.2, 0.83, 0.45, 0.9)
       PART_BUTTON("draw/proceed", "▶️", 0.55, 0.83, 0.81, 0.9)
     }
     programs {
@@ -124,8 +138,51 @@ collections {
   group {
     name: "train_result";
     parts {
-      PART_TITLE("train_result/title", "train successfully done")
-      PART_BUTTON("train_result/go_back", "go back", 0.1, 0.3, 0.9, 0.7)
+      part {
+        name: "train_result/epoch";
+        type: TEXT;
+        description {
+          state: "default" 0.0;
+          color: 255 255 255 255;
+          rel1.relative: 0.0 0.0;
+          rel2.relative: 1.0 0.3;
+          text {
+            text: "";
+            size: 58;
+            align: 0.5 0.5;
+          }
+        }
+      }
+      part {
+        name: "train_result/accuracy";
+        type: TEXT;
+        description {
+          state: "default" 0.0;
+          color: 255 255 255 255;
+          rel1.relative: 0.0 0.3;
+          rel2.relative: 1.0 0.6;
+          text {
+            text: "";
+            size: 78;
+            align: 0.5 0.5;
+          }
+        }
+      }
+      part {
+        name: "train_result/loss";
+        type: TEXT;
+        description {
+          state: "default" 0.0;
+          color: 255 255 255 255;
+          rel1.relative: 0.0 0.6;
+          rel2.relative: 1.0 0.9;
+          text {
+            text: "";
+            size: 58;
+            align: 0.5 0.5;
+          }
+        }
+      }
     }
   }
 }
index bcb21f3..cb454f6 100644 (file)
@@ -11,6 +11,7 @@
  *
  */
 #include "data.h"
+#include <regex.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -255,32 +256,57 @@ int data_extract_feature(appdata_s *ad, const char *dst, bool append) {
   return status;
 }
 
-void data_train_model() {
+void *data_run_model(void *data) {
+  appdata_s *ad = (appdata_s *)data;
+
+  int status = ML_ERROR_NONE;
+  int fd_stdout, fd_pipe;
+
   ml_train_model_h model;
 
   char model_conf_path[PATH_MAX];
   char label_path[PATH_MAX];
-  int status = ML_ERROR_NONE;
   FILE *file;
 
+  LOG_D("redirecting stdout");
+  fd_pipe = ad->pipe_fd[1];
+  fd_stdout = dup(1);
+  if (fd_stdout < 0) {
+    LOG_E("failed to duplicate stdout");
+    return NULL;
+  }
+
+  fflush(stdout);
+  if (dup2(fd_pipe, 1) < 0) {
+    LOG_E("failed to redirect fd pipe");
+    close(fd_stdout);
+    return NULL;
+  }
+
+  printf("test");
+
+  LOG_D("start running model");
   data_get_resource_path("model.ini", model_conf_path, false);
   data_get_data_path("label.dat", label_path);
 
+  LOG_D("opening file");
   file = fopen(label_path, "w");
   if (file == NULL) {
     LOG_E("Error opening file");
-    return;
+    return NULL;
   }
 
+  LOG_D("writing file");
   if (fputs("sad\nsmile\n\n", file) < 0) {
     LOG_E("error writing");
     fclose(file);
-    return;
+    return NULL;
   }
 
+  LOG_D("closing file");
   if (fclose(file) < 0) {
     LOG_E("Error closing file");
-    return;
+    return NULL;
   }
 
   LOG_D("model conf path: %s", model_conf_path);
@@ -288,7 +314,7 @@ void data_train_model() {
   status = ml_train_model_construct_with_conf(model_conf_path, &model);
   if (status != ML_ERROR_NONE) {
     LOG_E("constructing trainer model failed %d", status);
-    return;
+    return NULL;
   }
 
   status = ml_train_model_compile(model, NULL);
@@ -297,8 +323,6 @@ void data_train_model() {
     goto CLEAN_UP;
   }
 
-  freopen("out.txt", "a+", stdout);
-
   status = ml_train_model_run(model, NULL);
   if (status != ML_ERROR_NONE) {
     LOG_E("run model failed %d", status);
@@ -306,9 +330,101 @@ void data_train_model() {
   }
 
 CLEAN_UP:
+  // restore stdout
+  fflush(stdout);
+  dup2(fd_stdout, 1);
+  close(fd_pipe);
+
   status = ml_train_model_destroy(model);
   if (status != ML_ERROR_NONE) {
-    LOG_E("Destryoing model failed %d", status);
+    LOG_E("Destroying model failed %d", status);
+  }
+  return NULL;
+}
+
+int data_parse_result_string(const char *src, train_result_s *train_result) {
+  // clang-format off
+  // #10/10 - Training Loss: 0.398767 >> [ Accuracy: 75% - Validation Loss : 0.467543 ]
+  // clang-format on
+  int status = APP_ERROR_NONE;
+  int max_len = 512;
+  char buf[512];
+
+  regex_t pattern;
+  LOG_D(">>> %s", src);
+  char *pattern_string =
+    "#([0-9]+).*Training Loss: ([0-9]+\\.?[0-9]*)"
+    ".*Accuracy: ([0-9]+\\.?[0-9]*)%.*Validation Loss : ([0-9]+\\.?[0-9]*)";
+  unsigned int match_len = 5;
+  regmatch_t matches[5];
+
+  unsigned int i;
+
+  if (strlen(src) > max_len) {
+    LOG_E("source string too long");
+    return APP_ERROR_INVALID_PARAMETER;
+  }
+
+  status = regcomp(&pattern, pattern_string, REG_EXTENDED);
+  LOG_D(">>> %s", src);
+  if (status != 0) {
+    LOG_E("Could not compile regex string");
+    goto CLEAN;
+  }
+
+  status = regexec(&pattern, src, match_len, matches, 0);
+  switch (status) {
+  case 0:
+    break;
+  case REG_NOMATCH:
+    LOG_E("Nothing matches for given string");
+    goto CLEAN;
+  default:
+    LOG_E("Could not excuted regex, reason: %d", status);
+    goto CLEAN;
   }
-  return;
+
+  for (i = 1; i < match_len; ++i) {
+    if (matches[i].rm_so == -1) {
+      LOG_D("no information for idx: %d", i);
+      status = APP_ERROR_INVALID_PARAMETER;
+      goto CLEAN;
+    }
+
+    int len = matches[i].rm_eo - matches[i].rm_so;
+    if (len < 0) {
+      LOG_D("invalid length");
+      status = APP_ERROR_INVALID_PARAMETER;
+      goto CLEAN;
+    }
+
+    LOG_D("match start %d, match end: %d", matches[i].rm_so, matches[i].rm_eo);
+    memcpy(buf, src + matches[i].rm_so, len);
+    buf[len] = '\0';
+    LOG_D("Match %u: %s", i, buf);
+
+    switch (i) {
+    case 1:
+      train_result->epoch = atoi(buf);
+      break;
+    case 2:
+      train_result->train_loss = atof(buf);
+      break;
+    case 3:
+      train_result->accuracy = atof(buf);
+      break;
+    case 4:
+      train_result->valid_loss = atof(buf);
+      break;
+    default:
+      /// should not reach here
+      LOG_D("unknown index %d", i);
+      status = APP_ERROR_INVALID_PARAMETER;
+      goto CLEAN;
+    }
+  }
+CLEAN:
+  regfree(&pattern);
+
+  return status;
 }
index ddc67e2..c1a3005 100644 (file)
@@ -24,6 +24,12 @@ static bool app_create(void *data) {
 
   pthread_mutex_init(&ad->pipe_lock, NULL);
   pthread_cond_init(&ad->pipe_cond, NULL);
+  ad->data_output_pipe = ecore_pipe_add(view_update_result_cb, (void *)ad);
+  if (ad->data_output_pipe == NULL) {
+    LOG_E("making data out pipe failed");
+    free(data_path);
+    return false;
+  }
 
   data_get_resource_path(EDJ_PATH, ad->edj_path, false);
 
@@ -48,7 +54,8 @@ static void app_control(app_control_h app_control, void *data) {
 }
 
 static void app_pause(void *data) {
-  /* Take necessary actions when application becomes invisible. */
+  /* Take necessa
+  ry actions when application becomes invisible. */
 }
 
 static void app_resume(void *data) {
@@ -60,6 +67,7 @@ static void app_terminate(void *data) { /* Release all resources. */
 
   pthread_mutex_destroy(&ad->pipe_lock);
   pthread_cond_destroy(&ad->pipe_cond);
+  ecore_pipe_del(ad->data_output_pipe);
 }
 
 static void ui_app_lang_changed(app_event_info_h event_info, void *user_data) {
index 9ad488f..1fbdc54 100644 (file)
@@ -14,6 +14,7 @@ static Evas_Object *_create_layout(Evas_Object *parent, const char *edj_path,
                                    Eext_Event_Cb back_cb, void *user_data);
 
 static int _create_canvas(appdata_s *ad, const char *draw_mode);
+static int train(appdata_s *ad);
 
 static void _on_win_delete(void *data, Evas_Object *obj, void *event_info) {
   ui_app_exit();
@@ -106,6 +107,8 @@ int view_init(appdata_s *ad) {
   ad->win = win;
   ad->conform = conform;
 
+  ecore_pipe_freeze(ad->data_output_pipe);
+
   return status;
 }
 
@@ -144,12 +147,16 @@ int view_routes_to(appdata_s *ad, const char *group_name) {
     goto CLEAN_UP;
   }
 
+  elm_layout_signal_callback_add(ad->layout, "routes/to", "*", _on_routes_to,
+                                 ad);
+
   if (!strcmp(path, "draw")) {
     status = _create_canvas(ad, path_data);
   }
 
-  elm_layout_signal_callback_add(ad->layout, "routes/to", "*", _on_routes_to,
-                                 ad);
+  if (!strcmp(path, "train_result")) {
+    status = train(ad);
+  }
 
 CLEAN_UP:
   free(path);
@@ -204,6 +211,7 @@ static void _on_draw_start(void *data, Evas *e, Evas_Object *obj,
   LOG_D("x: %d, y: %d", eemd->canvas.x, eemd->canvas.y);
 
   cairo_set_source_rgba(ad->cr, 1, 1, 1, 1);
+  cairo_set_line_width(ad->cr, 5);
   cairo_move_to(ad->cr, eemd->canvas.x - ad->x_offset,
                 eemd->canvas.y - ad->y_offset);
 }
@@ -233,18 +241,18 @@ static void _on_canvas_exit(void *data, Evas *e, Evas_Object *obj,
   appdata_s *ad = (appdata_s *)data;
 
   evas_object_del(ad->canvas);
-  cairo_destroy(ad->cr);
-  if (cairo_status(ad->cr) != CAIRO_STATUS_SUCCESS) {
-    LOG_E("delete cairo failed");
-  }
   cairo_surface_destroy(ad->cr_surface);
   if (cairo_surface_status(ad->cr_surface) != CAIRO_STATUS_SUCCESS) {
     LOG_E("delete cr_surface failed");
   }
+  cairo_destroy(ad->cr);
+  if (cairo_status(ad->cr) != CAIRO_STATUS_SUCCESS) {
+    LOG_E("delete cairo failed");
+  }
 }
 
 static void _canvas_erase_all(appdata_s *ad) {
-  cairo_set_source_rgba(ad->cr, 0.5, 0.5, 0.5, 0.5);
+  cairo_set_source_rgba(ad->cr, 0.3, 0.3, 0.3, 0.2);
   cairo_set_operator(ad->cr, CAIRO_OPERATOR_SOURCE);
   cairo_paint(ad->cr);
   cairo_surface_flush(ad->cr_surface);
@@ -276,14 +284,14 @@ static void _on_draw_proceed(void *data, Evas_Object *obj, const char *emission,
   if (ad->tries == MAX_TRIES - 1) {
     ad->tries = 0;
     elm_naviframe_item_pop(ad->naviframe);
-    data_train_model();
     view_routes_to(ad, "train_result");
     return;
   }
 
-  sprintf(buf, "draw for %s [%d/%d]", ad->tries % NUM_CLASS ? "😊" : "😢",
-          ad->tries + 2, MAX_TRIES);
+  const char *emoji = ad->tries % NUM_CLASS ? "😊" : "😢";
+  sprintf(buf, "draw for %s [%d/%d]", emoji, ad->tries + 2, MAX_TRIES);
   elm_object_part_text_set(obj, "draw/title", buf);
+  elm_object_part_text_set(obj, "draw/label", emoji);
   LOG_D("starting extraction");
 
   ad->tries++;
@@ -350,7 +358,7 @@ static int _create_canvas(appdata_s *ad, const char *draw_mode) {
   }
 
   cairo_rectangle(cr, 0, 0, width, height);
-  cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 0.5);
+  cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 0.2);
   cairo_fill(cr);
   cairo_surface_flush(cairo_surface);
 
@@ -385,3 +393,95 @@ static int _create_canvas(appdata_s *ad, const char *draw_mode) {
 
   return APP_ERROR_NONE;
 }
+
+static void *process_train_result(void *data) {
+  appdata_s *ad = (appdata_s *)data;
+
+  // run model in another thread
+  int read_fd = ad->pipe_fd[0];
+  FILE *fp;
+  char buf[255];
+
+  fp = fdopen(read_fd, "r");
+
+  LOG_D("start waiting to get result");
+
+  ecore_pipe_thaw(ad->data_output_pipe);
+
+  while (fgets(buf, 255, fp) != NULL) {
+    if (ecore_pipe_write(ad->data_output_pipe, buf, 255) == false) {
+      LOG_E("pipe write error");
+      return NULL;
+    };
+    usleep(150);
+  }
+
+  LOG_D("training finished");
+  fclose(fp);
+  close(read_fd);
+  sleep(1);
+  ecore_pipe_freeze(ad->data_output_pipe);
+
+  return NULL;
+}
+
+static int train(appdata_s *ad) {
+  int status = ML_ERROR_NONE;
+
+  status = pipe(ad->pipe_fd);
+  if (status < 0) {
+    LOG_E("opening pipe for training failed");
+  }
+
+  ad->best_accuracy = 0.0;
+
+  LOG_D("creating thread to run model");
+  status = pthread_create(&ad->tid_writer, NULL, data_run_model, (void *)ad);
+  if (status < 0) {
+    LOG_E("creating pthread failed %s", strerror(errno));
+    return status;
+  }
+  status = pthread_detach(ad->tid_writer);
+  if (status < 0) {
+    LOG_E("detaching writing thread failed %s", strerror(errno));
+    pthread_cancel(ad->tid_writer);
+  }
+
+  status =
+    pthread_create(&ad->tid_reader, NULL, process_train_result, (void *)ad);
+  if (status < 0) {
+    LOG_E("creating pthread failed %s", strerror(errno));
+    return status;
+  }
+  status = pthread_detach(ad->tid_reader);
+  if (status < 0) {
+    LOG_E("detaching reading thread failed %s", strerror(errno));
+    pthread_cancel(ad->tid_writer);
+  }
+
+  return status;
+}
+
+void view_update_result_cb(void *data, void *buffer, unsigned int nbytes) {
+  appdata_s *ad = (appdata_s *)data;
+
+  char tmp[255];
+
+  train_result_s result;
+  if (data_parse_result_string(buffer, &result) != 0) {
+    LOG_W("parse failed. current buffer is being ignored");
+    return;
+  }
+
+  if (result.accuracy > ad->best_accuracy) {
+    ad->best_accuracy = result.accuracy;
+    snprintf(tmp, 255, "%.0f%%", ad->best_accuracy);
+    elm_object_part_text_set(ad->layout, "train_result/accuracy", tmp);
+  }
+
+  snprintf(tmp, 255, "%d tries", result.epoch);
+  elm_object_part_text_set(ad->layout, "train_result/epoch", tmp);
+
+  snprintf(tmp, 255, "Loss: %.2f", result.train_loss);
+  elm_object_part_text_set(ad->layout, "train_result/loss", tmp);
+}