/** * ml - a neural network processor written with C * Copyright (C) 2023 jvech * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include "util.h" #include "nn.h" #define MAX_FILE_SIZE 536870912 //1<<29; 0.5 GiB typedef struct Array { double *data; size_t shape[2]; } Array; #define ARRAY_SIZE(x, type) sizeof(x) / sizeof(type) static void json_read( const char *filepath, Array *input, Array *out, char *out_keys[], size_t out_keys_size, char *in_keys[], size_t in_keys_size, bool read_output); static void json_write( const char *filepath, Array input, Array out, char *out_keys[], size_t out_keys_size, char *in_keys[], size_t in_keys_size); void json_read( const char *filepath, Array *input, Array *out, char *out_keys[], size_t n_out_keys, char *in_keys[], size_t n_input_keys, bool read_output) { FILE *fp = NULL; static char fp_buffer[MAX_FILE_SIZE]; fp = (!strcmp(filepath, "-")) ? fopen("/dev/stdin", "r") : fopen(filepath, "r"); if (fp == NULL) goto json_read_error; size_t i = 0; do { if (i >= MAX_FILE_SIZE) die("json_read() Error: file size is bigger than '%zu'", i, MAX_FILE_SIZE); fp_buffer[i] = fgetc(fp); } while (fp_buffer[i++] != EOF); json_object *json_obj; json_obj = json_tokener_parse(fp_buffer); size_t json_obj_length = json_object_array_length(json_obj); input->shape[0] = (size_t)json_obj_length; input->shape[1] = n_input_keys; input->data = calloc(input->shape[0] * input->shape[1], sizeof(input->data[0])); out->shape[0] = (size_t)json_obj_length; out->shape[1] = n_out_keys; out->data = calloc(out->shape[0] * out->shape[1], sizeof(out->data[0])); if (!input->data || !out->data) goto json_read_error; for (int i = 0; i < json_object_array_length(json_obj); i++) { json_object *item = json_object_array_get_idx(json_obj, i); for (int j = 0; j < n_input_keys; j++) { size_t index = n_input_keys * i + j; input->data[index] = json_object_get_double(json_object_object_get(item, in_keys[j])); } if (!read_output) continue; for (int j = 0; j < n_out_keys; j++) { size_t index = n_out_keys * i + j; out->data[index] = json_object_get_double(json_object_object_get(item, out_keys[j])); } } json_object_put(json_obj); fclose(fp); return; json_read_error: perror("json_read() Error"); exit(1); } void json_write( const char *filepath, Array input, Array out, char *out_keys[], size_t out_keys_size, char *in_keys[], size_t in_keys_size) { FILE *fp = (!filepath) ? fopen("/dev/stdout", "w") : fopen(filepath, "w"); if (!fp) die("json_read() Error:"); fprintf(fp, "[\n"); for (size_t i = 0; i < input.shape[0]; i++) { fprintf(fp, " {\n"); for (size_t j = 0; j < input.shape[1]; j++) { size_t index = input.shape[1] * i + j; fprintf(fp, " \"%s\": %lf,\n", in_keys[j], input.data[index]); } for (size_t j = 0; j < out.shape[1]; j++) { size_t index = out.shape[1] * i + j; fprintf(fp, " \"%s\": %lf", out_keys[j], out.data[index]); if (j == out.shape[1] - 1) fprintf(fp, "\n"); else fprintf(fp, ",\n"); } if (i == input.shape[0] - 1) fprintf(fp, " }\n"); else fprintf(fp, " },\n"); } fprintf(fp, "]\n"); fclose(fp); } void load_config(struct Configs *cfg, int n_args, ...) { char *filepath; va_list ap; va_start(ap, n_args); int i; for (i = 0; i < n_args; i++) { filepath = va_arg(ap, char *); util_load_config(cfg, filepath); if (errno == 0) { va_end(ap); return; } else if (errno == ENOENT && i < n_args - 1) { errno = 0; } else break; } va_end(ap); die("load_config() Error:"); } Layer * load_network(struct Configs cfg) { extern struct Activation NN_RELU; extern struct Activation NN_SOFTPLUS; extern struct Activation NN_SIGMOID; extern struct Activation NN_LEAKY_RELU; extern struct Activation NN_LINEAR; extern struct Activation NN_TANH; Layer *network = ecalloc(cfg.network_size, sizeof(Layer)); for (size_t i = 0; i < cfg.network_size; i++) { if (!strcmp("relu", cfg.activations[i])) network[i].activation = NN_RELU; else if (!strcmp("sigmoid", cfg.activations[i])) network[i].activation = NN_SIGMOID; else if (!strcmp("softplus", cfg.activations[i])) network[i].activation = NN_SOFTPLUS; else if (!strcmp("leaky_relu", cfg.activations[i])) network[i].activation = NN_LEAKY_RELU; else if (!strcmp("linear", cfg.activations[i])) network[i].activation = NN_LINEAR; else if (!strcmp("tanh", cfg.activations[i])) network[i].activation = NN_TANH; else die("load_network() Error: Unknown '%s' activation", cfg.activations[i]); network[i].neurons = cfg.neurons[i]; } return network; } struct Cost load_loss(struct Configs cfg) { extern struct Cost NN_SQUARE; if (!strcmp("square", cfg.loss)) return NN_SQUARE; die("load_loss() Error: Unknown '%s' loss function", cfg.loss); exit(1); } int main(int argc, char *argv[]) { char default_config_path[512]; struct Configs ml_configs = { .epochs = 100, .alpha = 1e-5, .config_filepath = "utils/settings.cfg", .network_size = 0, .out_filepath = NULL, }; // First past to check if --config option was put util_load_cli(&ml_configs, argc, argv); optind = 1; // Load configs with different possible paths sprintf(default_config_path, "%s/%s", getenv("HOME"), ".config/ml/ml.cfg"); load_config(&ml_configs, 2, ml_configs.config_filepath, default_config_path); // re-read cli options again, to overwrite file configuration options util_load_cli(&ml_configs, argc, argv); argc -= optind; argv += optind; Layer *network = load_network(ml_configs); Array X, y; if (!strcmp("train", argv[0])) { json_read(argv[1], &X, &y, ml_configs.label_keys, ml_configs.n_label_keys, ml_configs.input_keys, ml_configs.n_input_keys, true); nn_network_init_weights(network, ml_configs.network_size, X.shape[1], true); nn_network_train( network, ml_configs.network_size, X.data, X.shape, y.data, y.shape, load_loss(ml_configs), ml_configs.epochs, ml_configs.alpha); nn_network_write_weights(ml_configs.weights_filepath, network, ml_configs.network_size); fprintf(stderr, "weights saved on '%s'\n", ml_configs.weights_filepath); } else if (!strcmp("predict", argv[0])) { json_read(argv[1], &X, &y, ml_configs.label_keys, ml_configs.n_label_keys, ml_configs.input_keys, ml_configs.n_input_keys, false); nn_network_init_weights(network, ml_configs.network_size, X.shape[1], false); nn_network_read_weights(ml_configs.weights_filepath, network, ml_configs.network_size); nn_network_predict(y.data, y.shape, X.data, X.shape, network, ml_configs.network_size); json_write(ml_configs.out_filepath, X, y, ml_configs.label_keys, ml_configs.n_label_keys, ml_configs.input_keys, ml_configs.n_input_keys); } else usage(1); nn_network_free_weights(network, ml_configs.network_size); free(network); util_free_config(&ml_configs); return 0; }