From 54d249af52a25f67f0b500f740efb83dc0ffb360 Mon Sep 17 00:00:00 2001 From: Jihoon Lee Date: Thu, 13 Aug 2020 11:34:26 +0900 Subject: [PATCH] [Example] Add display result **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 --- .../Tizen_native/CustomShortcut/inc/data.h | 41 ++++++- .../Tizen_native/CustomShortcut/inc/view.h | 8 ++ .../Tizen_native/CustomShortcut/res/edje/main.edc | 63 +++++++++- .../Tizen_native/CustomShortcut/src/data.c | 136 +++++++++++++++++++-- .../Tizen_native/CustomShortcut/src/main.c | 10 +- .../Tizen_native/CustomShortcut/src/view.c | 122 ++++++++++++++++-- 6 files changed, 350 insertions(+), 30 deletions(-) diff --git a/Applications/Tizen_native/CustomShortcut/inc/data.h b/Applications/Tizen_native/CustomShortcut/inc/data.h index a1bdb1c..d37c1ab 100644 --- a/Applications/Tizen_native/CustomShortcut/inc/data.h +++ b/Applications/Tizen_native/CustomShortcut/inc/data.h @@ -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" diff --git a/Applications/Tizen_native/CustomShortcut/inc/view.h b/Applications/Tizen_native/CustomShortcut/inc/view.h index 1befd30..827db54 100644 --- a/Applications/Tizen_native/CustomShortcut/inc/view.h +++ b/Applications/Tizen_native/CustomShortcut/inc/view.h @@ -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__ */ diff --git a/Applications/Tizen_native/CustomShortcut/res/edje/main.edc b/Applications/Tizen_native/CustomShortcut/res/edje/main.edc index ac0690e..eba4fc8 100644 --- a/Applications/Tizen_native/CustomShortcut/res/edje/main.edc +++ b/Applications/Tizen_native/CustomShortcut/res/edje/main.edc @@ -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; + } + } + } } } } diff --git a/Applications/Tizen_native/CustomShortcut/src/data.c b/Applications/Tizen_native/CustomShortcut/src/data.c index bcb21f3..cb454f6 100644 --- a/Applications/Tizen_native/CustomShortcut/src/data.c +++ b/Applications/Tizen_native/CustomShortcut/src/data.c @@ -11,6 +11,7 @@ * */ #include "data.h" +#include #include #include @@ -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; } diff --git a/Applications/Tizen_native/CustomShortcut/src/main.c b/Applications/Tizen_native/CustomShortcut/src/main.c index ddc67e2..c1a3005 100644 --- a/Applications/Tizen_native/CustomShortcut/src/main.c +++ b/Applications/Tizen_native/CustomShortcut/src/main.c @@ -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) { diff --git a/Applications/Tizen_native/CustomShortcut/src/view.c b/Applications/Tizen_native/CustomShortcut/src/view.c index 9ad488f..1fbdc54 100644 --- a/Applications/Tizen_native/CustomShortcut/src/view.c +++ b/Applications/Tizen_native/CustomShortcut/src/view.c @@ -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); +} -- 2.7.4