#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
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
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"
*/
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__ */
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;
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 {
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;
+ }
+ }
+ }
}
}
}
*
*/
#include "data.h"
+#include <regex.h>
#include <stdio.h>
#include <string.h>
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);
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);
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);
}
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;
}
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);
}
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) {
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) {
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();
ad->win = win;
ad->conform = conform;
+ ecore_pipe_freeze(ad->data_output_pipe);
+
return status;
}
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);
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);
}
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);
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++;
}
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);
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);
+}