diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6e92954 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +os: osx +language: c + +script: + - make lib_test_coverage + +after_success: + - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/README.md b/README.md index e01d7c8..563cfda 100644 --- a/README.md +++ b/README.md @@ -1 +1,18 @@ -# Netapix \ No newline at end of file +![LOGO](https://github.com/touchlane/Netapix/blob/master/assets/logo.svg) + +[![Build Status](https://travis-ci.org/pkondrashkov/c_travis_coverage.svg?branch=master)](https://travis-ci.org/pkondrashkov/c_travis_coverage) +[![codecov.io](https://codecov.io/gh/touchlane/Netapix/branch/master/graph/badge.svg)](https://codecov.io/gh/codecov/Netapix/branch/master) +![License](https://img.shields.io/badge/license-MIT-blue.svg) +![Platform](https://img.shields.io/badge/platform-MacOS%2FUbuntu-lightgrey.svg) + + +# Requirements + + +# Installation + + +# Usage + + +# Documentation diff --git a/example/config.mk b/example/config.mk new file mode 100644 index 0000000..1fbcfc7 --- /dev/null +++ b/example/config.mk @@ -0,0 +1,38 @@ +-include makefile + +# Source, Executable, Library Defines. +APP_SRC_DIR = example/src +APP_OBJ_DIR = example/obj +APP_SRC := $(subst $(APP_SRC_DIR)/,, $(shell find $(APP_SRC_DIR) -maxdepth 1 -name '*.c')) +APP_OBJ = $(APP_SRC:.c=.o) +EXEC_PATH = example/bin +EXEC = $(EXEC_PATH)/netapix + +# Compiler, Include, Linker Defines. +CC = gcc +APP_INCLUDE = -I./include/ -I.$(APP_SRC_DIR) +APP_CFLAGS = $(APP_INCLUDE) -w +LIBPATH = -L./lib -lnetapix +LFLAGS = -o $(EXEC) $(LIBPATH) + +example_app: example_mkdir $(EXEC) + +# Compile and Assemble C Source Files into Object Files. +$(APP_OBJ_DIR)/%.o: $(APP_SRC_DIR)/%.c + $(CC) $(APP_CFLAGS) -c $< -o $@ + +# Compile binary and link with external Libraries. +$(EXEC): $(addprefix $(APP_OBJ_DIR)/, $(APP_OBJ)) + $(CC) $(APP_CFLAGS) $(LFLAGS) $(addprefix $(APP_OBJ_DIR)/, $(APP_OBJ)) + +# Create obj directory for .o files. +example_mkdir: + mkdir -p $(APP_OBJ_DIR) + mkdir -p $(EXEC_PATH) + +# Clean Up Exectuable, Objects, Library, Coverage files d +example_clean: + rm -rf $(EXEC) $(APP_OBJ_DIR) + rm -rf $(APP_SRC:.c=.gcda) $(APP_SRC:.c=.gcno) + +.PHONY: example_all example_clean diff --git a/example/src/constants.h b/example/src/constants.h new file mode 100644 index 0000000..bdf3e22 --- /dev/null +++ b/example/src/constants.h @@ -0,0 +1,23 @@ +// +// constants.h +// netapix +// +// Created by Pavel Kondrashkov on 6/27/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef constants_h +#define constants_h + +#define FATAL_ERROR_NETAPIX_MODE_USAGE_FAIL_MSG "usage: %s [train | run]\n" +#define FATAL_ERROR_NETAPIX_MODE_TRAIN_USAGE_FAIL_MSG "usage: %s %s [path/config.npx] [path/dataset/*.npt] [path/weights.npw (optional)]\n" +#define FATAL_ERROR_NETAPIX_MODE_RUN_USAGE_FAIL_MSG "usage: %s %s [path/input.npi] [path/weights.npw] [path/output/ (optional)]\n" +#define FATAL_ERROR_REMOVING_OUTPUT_DIRECTORY_FAIL_MSG "Could not remove output directory %s.\n" +#define FATAL_ERROR_COPY_FILE_FAIL_MSG "Could not copy file from %s to %s.\n" + +#define ERROR_OUTPUT_DIRECTORY_EXISTS_MSG "Output directory %s exists. Override? Y\\N\n" +#define DEFAULT_OUTPUT_WEIGHTS_DIRECTORY_NAME "weights" +#define DEFAULT_OUTPUT_WEIGHTS_PARAMS_DIRECTORY_NAME "weights/params" +#define DEFAULT_OUTPUT_RUN_DIRECTORY_NAME "output" + +#endif /* constants_h */ diff --git a/example/src/netapix.c b/example/src/netapix.c new file mode 100644 index 0000000..14b3ee8 --- /dev/null +++ b/example/src/netapix.c @@ -0,0 +1,89 @@ +// +// main.c +// Parser +// +// Created by Evgeny Dedovets on 4/20/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "netapix.h" +#include +#include +#include + +#include "options.h" +#include "constants.h" + +int train_mode(int argc, char *argv[]); +int run_mode(int argc, char *argv[]); + +int main(int argc, char *argv[]) { + netapix_mode mode = determine_run_mode(argc, argv); + switch (mode) { + case TRAIN_MODE: + train_mode(argc, argv); + break; + case RUN_MODE: + run_mode(argc, argv); + break; + case NDF_MODE: + printf(FATAL_ERROR_NETAPIX_MODE_USAGE_FAIL_MSG, argv[0]); + return 0; + } + return 0; +} + +int train_mode(int argc, char *argv[]) { + if (argc < 4){ + printf(FATAL_ERROR_NETAPIX_MODE_TRAIN_USAGE_FAIL_MSG, argv[0], argv[1]); + return 1; + } + + char *npx_path = argv[2]; + char *train_path = argv[3]; + char *weights_path = (argc > 4) ? argv[4] : NULL; + + char *params_save_path = NULL; + char *output_path = NULL; + if (weights_path) { + output_path = make_output_save_path(weights_path, DEFAULT_OUTPUT_WEIGHTS_DIRECTORY_NAME); + params_save_path = make_output_save_path(weights_path, DEFAULT_OUTPUT_WEIGHTS_PARAMS_DIRECTORY_NAME); + } else { + output_path = make_output_save_path(npx_path, DEFAULT_OUTPUT_WEIGHTS_DIRECTORY_NAME); + params_save_path = output_path; + } + + if (prepare_output_path(output_path, 0) || prepare_output_path(params_save_path, 1)) { + return 0; + } + if (copy_param_files(npx_path, weights_path, params_save_path)) { + return 0; + } + + train(npx_path, train_path, weights_path, output_path); + return 0; +} + +int run_mode(int argc, char *argv[]) { + if (argc < 4){ + printf(FATAL_ERROR_NETAPIX_MODE_RUN_USAGE_FAIL_MSG, argv[0], argv[1]); + return 1; + } + char *input_path = argv[2]; + char *weights_path = argv[3]; + char *output_path = (argc > 4) ? argv[4] : NULL; + + output_path = make_output_save_path(output_path ? output_path : "./", DEFAULT_OUTPUT_RUN_DIRECTORY_NAME); + if (prepare_output_path(output_path, 1)) { + return 0; + } + + char *input_name = remove_ext(last_path_component(input_path)); + output_path = realloc(output_path, (strlen(output_path) + + strlen(input_name) + + strlen(".npo") + 1) * sizeof(*output_path)); + sprintf(output_path, "%s%s.npo", output_path, input_name); + + run(input_path, weights_path, output_path); + return 0; +} diff --git a/example/src/options.c b/example/src/options.c new file mode 100644 index 0000000..68b120e --- /dev/null +++ b/example/src/options.c @@ -0,0 +1,154 @@ +// +// options.c +// netapix +// +// Created by Pavel Kondrashkov on 6/27/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "options.h" +#include +#include +#include +#include +#include +#include + +#include "constants.h" + +netapix_mode determine_run_mode(int argc, char *argv[]) { + if (argc < 2) { + return NDF_MODE; + } + if (0 == strcmp(argv[1], "train")) { + return TRAIN_MODE; + } else if (0 == strcmp(argv[1], "run")) { + return RUN_MODE; + } + return NDF_MODE; +} + +char *remove_ext(char *path) { + size_t len = strlen(path); + size_t i; + size_t dot_index = -1; + char *copy_str = malloc((len + 1) * sizeof(*copy_str)); + strcpy(copy_str, path); + for (i = 0; i < len; ++i) { + if (path[i] == '.') { + dot_index = i; + } + } + if (dot_index != -1) { + copy_str[dot_index] = '\0'; + } + return copy_str; +} + +char *last_path_component(char *path) { + char *component = strrchr(path, '/'); + return component ? component + 1 : path; +} + +char *remove_last_path_component(char *path) { + size_t len = strlen(path); + char *copy_str = malloc((len + 1) * sizeof(*copy_str)); + strcpy(copy_str, path); + char *component = strrchr(copy_str, '/'); + *component = '\0'; + return copy_str; +} + +int unlink_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { + int rv = remove(path); + if (rv) { + printf(FATAL_ERROR_REMOVING_OUTPUT_DIRECTORY_FAIL_MSG, path); + } + return rv; +} + +int prepare_output_path(char *output_path, int force_create) { + if (!force_create && 0 == directory_exists(output_path)) { + char answer; + printf(ERROR_OUTPUT_DIRECTORY_EXISTS_MSG, output_path); + do { answer = fgetc(stdin); } while (answer != 'Y' && answer != 'N'); + if (answer == 'N') { + return 1; + } + nftw(output_path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS); + } + mkdir(output_path, S_IRWXU); + return 0; +} + +char *make_output_save_path(char *base_path, char *directory_name) { + char *output_base_path = remove_last_path_component(base_path); + char *output_path = malloc( (strlen(output_base_path) + + strlen(directory_name) + + strlen("//") + 1) * sizeof(*output_path) ); /// + 1 char for the null char. + sprintf(output_path, "%s/%s/", output_base_path, directory_name); + return output_path; +} + +int copy_param_files(char *config_file_path, char *weights_file_path, char *output_path) { + if (config_file_path) { + char *config_file_name = "config.npx"; + char *config_copy_file_path = malloc((strlen(output_path) + + strlen(config_file_name) + 1) * sizeof(char)); + sprintf(config_copy_file_path, "%s%s", output_path, config_file_name); + if (copy_file(config_file_path, config_copy_file_path)) { + return 1; + } + } + if (weights_file_path) { + char *weights_file_name = last_path_component(weights_file_path); + char *weights_copy_file_path = malloc((strlen(output_path) + + strlen(weights_file_name) + 1) * sizeof(char)); + sprintf(weights_copy_file_path, "%s%s", output_path, weights_file_name); + if (copy_file(weights_file_path, weights_copy_file_path)) { + return 1; + } + } + return 0; +} + +int copy_file(char *from_path, char *to_path) { + FILE *from_file, *to_file; + from_file = fopen(from_path, "rb"); + if (!from_file) { + return 1; + } + to_file = fopen(to_path, "wb"); + if (!to_file) { + fclose(from_file); + return 1; + } + size_t read_data, write_data; + unsigned char buff[8192]; + do { + read_data = fread(buff, 1, sizeof(buff), from_file); + if (read_data) { + write_data = fwrite(buff, 1, read_data, to_file); + } else { + write_data = 0; + } + } while ((read_data > 0) && (read_data == write_data)); + if (write_data) { + fclose(from_file); + fclose(to_file); + return 1; + } + fclose(from_file); + fclose(to_file); + return 0; +} + +int directory_exists(char *path) { + DIR *dir = opendir(path); + if (dir) { + /// Directory exists. + closedir(dir); + return 0; + } + return 1; +} diff --git a/example/src/options.h b/example/src/options.h new file mode 100644 index 0000000..ccc046f --- /dev/null +++ b/example/src/options.h @@ -0,0 +1,29 @@ +// +// options.h +// netapix +// +// Created by Pavel Kondrashkov on 6/27/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef options_h +#define options_h + +#include + +typedef enum { + TRAIN_MODE, + RUN_MODE, + NDF_MODE +} netapix_mode; + +netapix_mode determine_run_mode(int argc, char *argv[]); +char *make_output_save_path(char *base_path, char *output_name); +char *remove_ext(char *path); +char *last_path_component(char *path); +int directory_exists(char *path); +int prepare_output_path(char *output_path, int force_create); +int copy_file(char *from_path, char *to_path); +int copy_param_files(char *config_file_path, char *weights_file_path, char *output_path); + +#endif /* options_h */ diff --git a/example/tests/config.mk b/example/tests/config.mk new file mode 100644 index 0000000..01e78e4 --- /dev/null +++ b/example/tests/config.mk @@ -0,0 +1,35 @@ +-include example/config.mk + +# Source, Executable, Library Defines. +APP_TEST_DIR = example/tests/src +APP_SRC_DIR = example/src +TEST_SRC := $(subst $(APP_TEST_DIR)/,, $(shell find $(APP_TEST_DIR) -name '*.c')) +TEST_EXEC_PATH = example/tests/bin +TEST_EXEC = $(TEST_EXEC_PATH)/test + +# Compiler, Include, Linker Defines. +CC = gcc +APP_INCLUDE = -I./include/ -I./$(APP_SRC_DIR) +APP_CFTEST = $(APP_INCLUDE) -w -O0 -std=c99 -o $(TEST_EXEC) + +# Create a test running Executable. +example_test: example_test_mkdir + $(CC) $(APP_CFTEST) $(addprefix $(APP_TEST_DIR)/, $(TEST_SRC)) $(addprefix $(APP_SRC_DIR)/, $(filter-out netapix.c, $(APP_SRC))) + example/tests/bin/test + +# Create a test running Executable with coverage turned on. +example_test_coverage: example_test_mkdir + $(CC) $(APP_CFTEST) -coverage $(addprefix $(APP_TEST_DIR)/, $(TEST_SRC)) $(addprefix $(APP_SRC_DIR)/, $(filter-out netapix.c, $(APP_SRC))) + @rm -rf $(TEST_SRC:.c=.gcda) $(TEST_SRC:.c=.gcno) + example/tests/bin/test + +# Create obj directory for bin file. +example_test_mkdir: + mkdir -p $(TEST_EXEC_PATH) + +# Clean Up Exectuable, Objects, Library, Coverage files d +example_test_clean: + rm -rf $(TEST_EXEC_PATH) + rm -rf $(TEST_SRC:.c=.gcda) $(TEST_SRC:.c=.gcno) + +.PHONY: example_test example_test_clean \ No newline at end of file diff --git a/example/tests/src/test.c b/example/tests/src/test.c new file mode 100644 index 0000000..c7f55e9 --- /dev/null +++ b/example/tests/src/test.c @@ -0,0 +1,23 @@ +// +// test.c +// netapix +// +// Created by Pavel Kondrashkov on 5/22/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include +#include "unit_test.h" +#include "test_options.h" + +int main() { + test(test_options_determine_run_mode_defined, "utils test determine run mode defined."); + test(test_options_determine_run_mode_not_defined, "utils test determine run mode not defined."); + test(test_options_determine_run_mode_not_specified, "utils test determine run mode not specified."); + test(test_options_remove_ext, "utils test remove file extension from path."); + test(test_options_last_path_component, "utils test last path component from path."); + test(test_options_make_output_save_path, "utils test make output save path."); + + printf("PASSED: %d\nFAILED: %d\n", test_passed, test_failed); + return (test_failed > 0); +} diff --git a/example/tests/src/test_options.c b/example/tests/src/test_options.c new file mode 100644 index 0000000..8ff43ac --- /dev/null +++ b/example/tests/src/test_options.c @@ -0,0 +1,87 @@ +// +// test_options.c +// netapix +// +// Created by Pavel Kondrashkov on 6/27/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "test_options.h" +#include "unit_test.h" +#include "options.h" + +int test_options_determine_run_mode_defined(void) { + int argc = 2; + char *argv[] = { "netapix", "train" }; + + netapix_mode mode = determine_run_mode(argc, argv); + + assert_equal(mode, TRAIN_MODE); + return 0; +} + +int test_options_determine_run_mode_not_defined(void) { + int argc = 2; + char *argv[] = { "netapix", "somemode" }; + + netapix_mode mode = determine_run_mode(argc, argv); + + assert_equal(mode, NDF_MODE); + return 0; +} + +int test_options_determine_run_mode_not_specified(void) { + int argc = 1; + char *argv[] = { "netapix" }; + + netapix_mode mode = determine_run_mode(argc, argv); + + assert_equal(mode, NDF_MODE); + return 0; +} + +int test_options_remove_ext(void) { + char str1[] = "../../path/folder/config.npx"; + char str2[] = "./config.npx"; + char str3[] = "/config.npx"; + char str4[] = "config"; + + char *result1 = remove_ext(str1); + char *result2 = remove_ext(str2); + char *result3 = remove_ext(str3); + char *result4 = remove_ext(str4); + + assert_equal_string(result1, "../../path/folder/config"); + assert_equal_string(result2, "./config"); + assert_equal_string(result3, "/config"); + assert_equal_string(result4, "config"); + return 0; +} + +int test_options_last_path_component(void) { + char str1[] = "../../path/folder/config.npx"; + char str2[] = "./config.npx"; + char str3[] = "/config.npx"; + char str4[] = "config.npx"; + + char *result1 = last_path_component(str1); + char *result2 = last_path_component(str2); + char *result3 = last_path_component(str3); + char *result4 = last_path_component(str4); + + assert_equal_string(result1, "config.npx"); + assert_equal_string(result2, "config.npx"); + assert_equal_string(result3, "config.npx"); + assert_equal_string(result4, "config.npx"); + return 0; +} + +int test_options_make_output_save_path(void) { + char *base_path = "./"; + char *output_name = "output"; + + char *result = make_output_save_path(base_path, output_name); + + assert_equal_string(result, "./output/"); + return 0; +} diff --git a/example/tests/src/test_options.h b/example/tests/src/test_options.h new file mode 100644 index 0000000..7931a75 --- /dev/null +++ b/example/tests/src/test_options.h @@ -0,0 +1,21 @@ +// +// test_options.h +// netapix +// +// Created by Pavel Kondrashkov on 6/27/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef test_options_h +#define test_options_h + +#include + +int test_options_determine_run_mode_defined(void); +int test_options_determine_run_mode_not_defined(void); +int test_options_determine_run_mode_not_specified(void); +int test_options_remove_ext(void); +int test_options_last_path_component(void); +int test_options_make_output_save_path(void); + +#endif /* test_options_h */ diff --git a/example/tests/src/unit_test.c b/example/tests/src/unit_test.c new file mode 100644 index 0000000..b321eac --- /dev/null +++ b/example/tests/src/unit_test.c @@ -0,0 +1,23 @@ +// +// unit_test.c +// netapix +// +// Created by Pavel Kondrashkov on 5/24/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include +#include "unit_test.h" + +int test_passed = 0; +int test_failed = 0; + +void test(int (*func)(void), const char *name) { + int r = func(); + if (r == 0) { + test_passed++; + } else { + test_failed++; + printf("FAILED: %s (at line %d)\n", name, r); + } +} diff --git a/example/tests/src/unit_test.h b/example/tests/src/unit_test.h new file mode 100644 index 0000000..48af029 --- /dev/null +++ b/example/tests/src/unit_test.h @@ -0,0 +1,35 @@ +// +// unit_test.h +// netapix +// +// Created by Pavel Kondrashkov on 5/24/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef unit_test_h +#define unit_test_h + +#include +#include + +extern int test_passed; +extern int test_failed; + +/* Terminate current test with error */ +#define fail() return __LINE__ + +/* Successful end of the test case */ +#define done() return 0 + +/* Assert defines */ +#define assert(cond) do { if (!(cond)) fail(); } while (0) +#define assert_not_equal(a, b) assert( a != b ) +#define assert_equal(a, b) assert( a == b ) +#define assert_equal_float(a, b) assert( (fabs(a - b) < 1e-7) ) +#define assert_equal_string(a, b) assert( !strcmp((const char*)a, (const char*)b) ) +#define assert_equal_ptr(a, b) assert( (void*)(a) == (void*)(b)) + +/* Test runner */ +void test(int (*func)(void), const char *name); + +#endif /* unit_test_h */ diff --git a/include/netapix.h b/include/netapix.h new file mode 100644 index 0000000..41eaffd --- /dev/null +++ b/include/netapix.h @@ -0,0 +1,15 @@ +// +// netapix.h +// netapix +// +// Created by Pavel Kondrashkov on 5/29/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef netapix_h +#define netapix_h + +int train(char *npx_path, char *train_path, char *weights_path, char *output_path); +int run(char *npi_path, char *weights_path, char *output_path); + +#endif /* netapix_h */ diff --git a/makefile b/makefile new file mode 100644 index 0000000..10bb833 --- /dev/null +++ b/makefile @@ -0,0 +1,66 @@ +# Source, Executable, Library Defines. +LIB_SRC_DIR = src +OBJ_DIR = obj +LIB_DIR = lib +LIB_SRC := $(subst $(LIB_SRC_DIR)/,, $(shell find $(LIB_SRC_DIR) -maxdepth 1 -name '*.c')) +LIB_OBJ = $(LIB_SRC:.c=.o) +ALIB = libnetapix.a + +# Compiler, Include, Linker Defines. +CC = gcc +LIB_INCLUDE = -I./include/ -I./src/ +LIB_CFLAGS = $(LIB_INCLUDE) -w + +all: example + +# Compile and Assemble C Source Files into Object Files. +%.o: $(LIB_SRC_DIR)/%.c + $(CC) $(LIB_CFLAGS) -c $< -o $(OBJ_DIR)/$@ + +# Assemble static library from Object Files. +$(ALIB): $(LIB_OBJ) + ar rcs $(addprefix $(LIB_DIR)/, $@) $(addprefix $(OBJ_DIR)/, $^) + +# Create obj directory for .o files. +lib_mkdir: + mkdir -p $(OBJ_DIR) + mkdir -p $(LIB_DIR) + +# Create all requred directories and compile a static library. +lib: lib_mkdir $(ALIB) + +# Call example's makefile to compile example app. +example: lib + make -f ./example/config.mk example_app + +# Call lib's tests makefile to compile test executable and run tests. +lib_test: + make -f ./tests/config.mk lib_test + +# Call lib's tests makefile to compile test executable and run tests with code coverage generation. +lib_test_coverage: + make -f ./tests/config.mk lib_test_coverage + +# Call example's tests makefile to compile test executable and run tests. +example_test: + make -f ./example/tests/config.mk example_test + +# Call example's tests makefile to compile test executable and run tests with code coverage generation. +example_test_coverage: + make -f ./example/tests/config.mk example_test_coverage + +# Call both example's tests and lib's tests. +test: example_test lib_test + +# Clean Up Library, Coverage files. +lib_clean: + rm -rf $(OBJ_DIR) $(LIB_DIR) + rm -rf $(LIB_SRC:.c=.gcda) $(LIB_SRC:.c=.gcno) + +# Call all clean up dependencies to perform full clean. +clean: lib_clean + make -f ./example/config.mk example_clean + make -f ./example/tests/config.mk example_test_clean + make -f ./tests/config.mk lib_test_clean + +.PHONY: lib example lib_test example_test test clean diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..724cba2 --- /dev/null +++ b/src/config.c @@ -0,0 +1,626 @@ +// +// config.c +// netapix +// +// Created by Evgeny Dedovets on 4/20/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "config.h" +#include +#include "utils.h" +#include "constants.h" +#include +#include + +typedef enum { + PARSING_CONFIG, + PARSING_LAYER, + NDEF_PARSING +} parsing_level; + +int pick_convolutional_output_size(int input_size, int filter_size, int padding, int stride) { + int size = 1; + while (size * stride - stride <= input_size - filter_size + 2 * padding) { + if (size * stride - stride == input_size - filter_size + 2 * padding) { + return size; + } else { + size = size + 1; + } + } + return -1; +} + +int init_inp_out(npx_config *npx) { + int convolutional_allowed = 1; + switch (npx->net[0].type) { + case CONNECTED: + convolutional_allowed = 0; + npx->settings->input_length = npx->net[0].input_length; + break; + case CONVOLUTIONAL: + npx->net[0].input_depth = npx->settings->channels; + npx->net[0].input_height = npx->settings->height; + npx->net[0].input_width = npx->settings->width; + npx->settings->input_length = npx->settings->channels * npx->settings->width * npx->settings->height; + break; + default: + printf(ERROR_FORMAT_LAYER_TYPE_IS_NOT_CORRECT_MSG, 0); + return ERROR_FORMAT; + break; + } + for (int i = 0; i < npx->size; i++) { + switch (npx->net[i].type) { + case CONNECTED: + convolutional_allowed = 0; + npx->net[i].output_length = npx->net[i + 1].input_length; + break; + case CONVOLUTIONAL: + if (convolutional_allowed > 0) { + if (i > 0) { + npx->net[i].input_depth = npx->net[i - 1].channels; + npx->net[i].input_height = npx->net[i - 1].output_height; + npx->net[i].input_width = npx->net[i - 1].output_width; + } + npx->net[i].output_width = pick_convolutional_output_size(npx->net[i].input_width, npx->net[i].width, npx->net[i].padding, npx->net[i].stride); + npx->net[i].output_height = pick_convolutional_output_size(npx->net[i].input_height, npx->net[i].height, npx->net[i].padding, npx->net[i].stride); + if (npx->net[i].output_width < 0 || npx->net[i].output_height < 0) { + printf(ERROR_FORMAT_BINDING_LAYERS_MSG, i - 1, i); + return ERROR_FORMAT; + } + npx->net[i].output_length = npx->net[i].output_height * npx->net[i].output_width * npx->net[i].channels; + if (npx->net[i + 1].type == CONNECTED && npx->net[i + 1].input_length != npx->net[i].output_length) { + printf(ERROR_FORMAT_BINDING_LAYERS_MSG, i, i + 1); + return ERROR_FORMAT; + } + } else { + printf(ERROR_FORMAT_CONVOLUTIONAL_AFTER_CONNECTED_MSG); + return ERROR_FORMAT; + } + break; + case LOSS: + if (npx->net[i - 1].output_length != npx->net[i].input_length) { + printf(ERROR_FORMAT_BINDING_LAYERS_MSG, i - 1, i); + return ERROR_FORMAT; + } else { + npx->settings->target_length = npx->net[i].input_length; + } + break; + case NDEF_LAYER: + printf(ERROR_FORMAT_LAYER_TYPE_IS_NOT_CORRECT_MSG, 0); + return ERROR_FORMAT; + break; + } + } + return 0; +} + +int validate_npx(npx_config *npx) { + if (validate_settings(npx->settings) < 0) { + return ERROR_FORMAT; + } + if (npx->size > 0) { + if (npx->net[0].type == CONVOLUTIONAL) { + if (npx->settings->channels == DEFAULT_CHANNELS_VALUE) { + printf(ERROR_FORMAT_MISSING_CHANNELS_MSG, CONVOLUTIONAL_LAYER_KEY); + return ERROR_FORMAT; + } else if (npx->settings->channels < 1) { + printf(ERROR_FORMAT_NOT_VALID_CHANNELS_MSG, npx->settings->channels); + return ERROR_FORMAT; + } + if (npx->settings->width == DEFAULT_SIZE_VALUE) { + printf(ERROR_FORMAT_MISSING_INPUT_WIDTH_MSG, CONVOLUTIONAL_LAYER_KEY); + return ERROR_FORMAT; + } else if (npx->settings->width < 1) { + printf(ERROR_FORMAT_NOT_VALID_INPUT_WIDTH_MSG, npx->settings->width); + return ERROR_FORMAT; + } + if (npx->settings->height == DEFAULT_SIZE_VALUE) { + printf(ERROR_FORMAT_MISSING_INPUT_HEIGHT_MSG, CONVOLUTIONAL_LAYER_KEY); + return ERROR_FORMAT; + } else if (npx->settings->height < 1) { + printf(ERROR_FORMAT_NOT_VALID_INPUT_HEIGHT_MSG, npx->settings->height); + return ERROR_FORMAT; + } + } + if (npx->size < 2) { + printf(ERROR_FORMAT_LAYERS_COUNT_MSG); + return ERROR_FORMAT; + } + for(int i = 0; i < npx->size; i++) { + if (validate_layer(npx->net[i], i, npx->size) < 0) { + return ERROR_FORMAT; + } + } + if (npx->net[npx->size - 1].loss == CROSS_ENTROPY && npx->net[npx->size - 2].activation != SOFTMAX) { + printf(ERROR_FORMAT_SOFTMAX_REQUIRED_MSG); + return ERROR_FORMAT; + } + } else { + printf(ERROR_FORMAT_NO_LAYERS_MSG); + return ERROR_FORMAT; + } + if (init_inp_out(npx) < 0) { + return ERROR_FORMAT; + } + return 0; +} + +int validate_layer(layer_config layer, int index, int count) { + int is_valid = 1; + if (layer.activation == SOFTMAX) { + if (index != count - 2) { + is_valid = 0; + printf(ERROR_FORMAT_SOFTMAX_NOT_ALLOWED_MSG); + } + } + switch (layer.type) { + case CONNECTED: + if (layer.input_length == DEFAULT_INPUT_SIZE_VALUE) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_INPUT_SIZE_MSG, CONNECTED_LAYER_KEY, index); + } else if (layer.input_length < 1) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_INPUT_SIZE_MSG, CONNECTED_LAYER_KEY, index, layer.input_length); + } + if (layer.activation == NDEF_ACTIVATION) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_ACTIVATION_MSG, CONNECTED_LAYER_KEY, index); + } + break; + case LOSS: + if (index != count - 1) { + is_valid = 0; + printf(ERROR_FORMAT_LOSS_POSITION_MSG, LOSS_LAYER_KEY); + } + if (layer.input_length == DEFAULT_INPUT_SIZE_VALUE) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_INPUT_SIZE_MSG, LOSS_LAYER_KEY, index); + } else if (layer.input_length < 1) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_INPUT_SIZE_MSG, LOSS_LAYER_KEY, index, layer.input_length); + } + if (layer.loss == NDEF_LOSS) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_ACTIVATION_MSG, CONNECTED_LAYER_KEY, index); + } + break; + case CONVOLUTIONAL: + if (layer.activation == NDEF_ACTIVATION) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_ACTIVATION_MSG, CONVOLUTIONAL_LAYER_KEY, index); + } + if (layer.width == DEFAULT_FILTER_SIZE_VALUE) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_FILTER_WIDTH_MSG, CONVOLUTIONAL_LAYER_KEY, index); + } else if (layer.width < 1) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_FILTER_WIDTH_MSG, CONVOLUTIONAL_LAYER_KEY, index, layer.width); + } + if (layer.height == DEFAULT_FILTER_SIZE_VALUE) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_FILTER_HEIGHT_MSG, CONVOLUTIONAL_LAYER_KEY, index); + } else if (layer.height < 1) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_FILTER_HEIGHT_MSG, CONVOLUTIONAL_LAYER_KEY, index, layer.height); + } + if (layer.channels == DEFAULT_FILTERS_COUNT_VALUE) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_FILTERS_COUNT_MSG, CONVOLUTIONAL_LAYER_KEY, index); + } else if (layer.channels < 1) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_FILTERS_COUNT_MSG, CONVOLUTIONAL_LAYER_KEY, index, layer.channels); + } + if (layer.padding == DEFAULT_PADDING_VALUE) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_PADDING_MSG, CONVOLUTIONAL_LAYER_KEY, index); + } else if (layer.padding < 0) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_PADDING_MSG, CONVOLUTIONAL_LAYER_KEY, index, layer.padding); + } + if (layer.stride == DEFAULT_STRIDE_VALUE) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_STRIDE_MSG, CONVOLUTIONAL_LAYER_KEY, index); + } else if (layer.stride < 1) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_STRIDE_MSG, CONVOLUTIONAL_LAYER_KEY, index, layer.stride); + } + break; + case NDEF_LAYER: + is_valid = 0; + break; + } + if (is_valid == 0) { + return ERROR_FORMAT; + } + return 0; +} + +int validate_settings(settings_config *config) { + int is_valid = 1; + if (config->batch < 1) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_BATCH_MSG, config->batch); + } + if (config->validation < 1) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_VALIDATION_MSG, config->validation); + } + if (config->save_frequency < 1) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_SAVE_FREQUENCY_MSG, config->save_frequency); + } + if (config->threads < 1) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_THREADS_MSG, config->batch); + } + if (config->threads > config->batch) { + is_valid = 0; + printf(ERROR_FORMAT_BATCH_LESS_THEN_THREADS_MSG, config->threads, config->batch); + } + if (config->accuracy == DEFAULT_ACCURACY_VALUE) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_ACCURACY_MSG); + } else if (config->accuracy <= 0) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_ACCURACY_MSG, config->accuracy); + } + switch (config->init) { + case NDEF_INITIALIZATION: + printf(ERROR_FORMAT_MISSING_INIT_TYPE_MSG); + is_valid = 0; + break; + default: + break; + } + switch (config->learning) { + case GRADIENT_DESCENT: + if (config->eta == DEFAULT_ETA_VALUE) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_ETA_MSG); + } else if (config->eta <= 0) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_ETA_MSG, config->eta); + } + if (config->alpha == DEFAULT_ALPHA_VALUE) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_ALPHA_MSG); + } else if (config->alpha > 1 || config->alpha <= 0) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_ALPHA_MSG, config->alpha); + } + if (config->beta == DEFAULT_BETA_VALUE) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_BETA_MSG); + } else if (config->beta <= 1) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_BETA_MSG, config->beta); + } + if (config->gamma == DEFAULT_GAMMA_VALUE) { + is_valid = 0; + printf(ERROR_FORMAT_MISSING_GAMMA_MSG); + } else if (config->gamma <= 0) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_GAMMA_MSG, config->gamma); + } + if (config->momentum < 0) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_MOMENTUM_MSG, config->momentum); + } + break; + case NDEF_LEARN: + is_valid = 0; + printf(ERROR_FORMAT_MISSING_LEARNING_TYPE_MSG); + break; + } + switch (config->regularization) { + case L1: + if (config->lambda < 0) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_LAMBDA_MSG, config->lambda); + } + break; + case L2: + if (config->lambda < 0) { + is_valid = 0; + printf(ERROR_FORMAT_NOT_VALID_LAMBDA_MSG, config->lambda); + } + break; + case NDEF_REGULARIZATION: + break; + } + if (is_valid == 0) { + return ERROR_FORMAT; + } + return 0; +} + +npx_config *read_npx(const char *filename) { + npx_config *npx = make_npx_config(); + char *buffer = NULL; + unsigned long length = 0; + int read_res = read_txt(filename, &buffer, &length); + if (read_res < 0) { + return npx; + } + int layers_count = 0; + npx_option *head = parse_npx(buffer, length, &layers_count); + npx->net = malloc(layers_count * sizeof(layer_config)); + npx->size = layers_count; + int l_index = 0; + parsing_level level = NDEF_PARSING; + npx_option *curr_opt = head; + layer_config *layer = NULL; + while (curr_opt != NULL) { + if (strcmp(curr_opt->key, CONFIG_KEY) == 0) { + level = PARSING_CONFIG; + } else if (is_layer(curr_opt->key) > 0) { + level = PARSING_LAYER; + npx->net[l_index] = make_layer_config(); + layer = &npx->net[l_index]; + layer->type = detect_layer_type(curr_opt->key); + l_index++; + } else { + switch (level) { + case PARSING_CONFIG: + if (strcmp(curr_opt->key, BATCH_KEY) == 0) { + sscanf(curr_opt->val, "%d", &npx->settings->batch); + } else if (strcmp(curr_opt->key, THREADS_KEY) == 0) { + sscanf(curr_opt->val, "%d", &npx->settings->threads); + } else if (strcmp(curr_opt->key, LEARNING_KEY) == 0) { + npx->settings->learning = detect_learning_type(curr_opt->val); + } else if (strcmp(curr_opt->key, REGULARIZATION_KEY) == 0) { + npx->settings->regularization = detect_regularization_type(curr_opt->val); + } else if (strcmp(curr_opt->key, ETA_KEY) == 0) { + sscanf(curr_opt->val, "%f", &npx->settings->eta); + } else if (strcmp(curr_opt->key, ACCURACY_KEY) == 0) { + sscanf(curr_opt->val, "%f", &npx->settings->accuracy); + } else if (strcmp(curr_opt->key, VALIDATION_KEY) == 0) { + sscanf(curr_opt->val, "%d", &npx->settings->validation); + } else if (strcmp(curr_opt->key, SAVE_FREQUENCY_KEY) == 0) { + sscanf(curr_opt->val, "%d", &npx->settings->save_frequency); + } else if (strcmp(curr_opt->key, LAMBDA_KEY) == 0) { + sscanf(curr_opt->val, "%f", &npx->settings->lambda); + } else if (strcmp(curr_opt->key, MOMENTUM_KEY) == 0) { + sscanf(curr_opt->val, "%f", &npx->settings->momentum); + } else if (strcmp(curr_opt->key, ALPHA_KEY) == 0) { + sscanf(curr_opt->val, "%f", &npx->settings->alpha); + } else if (strcmp(curr_opt->key, BETA_KEY) == 0) { + sscanf(curr_opt->val, "%f", &npx->settings->beta); + } else if (strcmp(curr_opt->key, GAMMA_KEY) == 0) { + sscanf(curr_opt->val, "%f", &npx->settings->gamma); + } else if (strcmp(curr_opt->key, CHANNELS_KEY) == 0) { + sscanf(curr_opt->val, "%d", &npx->settings->channels); + } else if (strcmp(curr_opt->key, INIT_KEY) == 0) { + npx->settings->init = detect_init_type(curr_opt->val); + } else if (strcmp(curr_opt->key, WIDTH_KEY) == 0) { + sscanf(curr_opt->val, "%d", &npx->settings->width); + } else if (strcmp(curr_opt->key, HEIGHT_KEY) == 0) { + sscanf(curr_opt->val, "%d", &npx->settings->height); + } + break; + case PARSING_LAYER: + if (strcmp(curr_opt->key, INPUT_KEY) == 0) { + sscanf(curr_opt->val, "%d", &layer->input_length); + } else if (strcmp(curr_opt->key, ACTIVATION_KEY) == 0) { + if (layer->type == LOSS) { + layer->loss = detect_loss_type(curr_opt->val); + } else { + layer->activation = detect_activation_type(curr_opt->val); + } + } else if (strcmp(curr_opt->key, STRIDE_KEY) == 0) { + sscanf(curr_opt->val, "%d", &layer->stride); + } else if (strcmp(curr_opt->key, CHANNELS_KEY) == 0) { + sscanf(curr_opt->val, "%d", &layer->channels); + } else if (strcmp(curr_opt->key, WIDTH_KEY) == 0) { + sscanf(curr_opt->val, "%d", &layer->width); + } else if (strcmp(curr_opt->key, HEIGHT_KEY) == 0) { + sscanf(curr_opt->val, "%d", &layer->height); + } else if (strcmp(curr_opt->key, PADDING_KEY) == 0) { + sscanf(curr_opt->val, "%d", &layer->padding); + } + break; + case NDEF_PARSING: + break; + } + } + curr_opt = curr_opt->next; + } + free(buffer); + free_npx_option(head); + return npx; +} + +npx_config *make_npx_config(void) { + npx_config *npx = malloc(sizeof(*npx)); + npx->net = NULL; + npx->size = 0; + npx->settings = make_settings_config(); + return npx; +} + +int free_npx_config(npx_config *npx) { + free_settings_config(npx->settings); + free(npx->net); + free(npx); + return 0; +} + +settings_config *make_settings_config() { + settings_config *settings = malloc(sizeof(*settings)); + settings->batch = DEFAULT_BATCH_VALUE; + settings->threads = DEFAULT_THREADS_VALUE; + settings->eta = DEFAULT_ETA_VALUE; + settings->accuracy = DEFAULT_ACCURACY_VALUE; + settings->validation = DEFAULT_VALIDATION_VALUE; + settings->gamma = DEFAULT_GAMMA_VALUE; + settings->alpha = DEFAULT_ALPHA_VALUE; + settings->beta = DEFAULT_BETA_VALUE; + settings->learning = NDEF_LEARN; + settings->momentum = DEFAULT_MOMENTUM_VALUE; + settings->regularization = NDEF_REGULARIZATION; + settings->lambda = DEFAULT_LAMBDA_VALUE; + settings->channels = DEFAULT_CHANNELS_VALUE; + settings->init = NDEF_INITIALIZATION; + settings->input_length = DEFAULT_NETWORK_INPUT_SIZE_VALUE; + settings->target_length = DEFAULT_NETWORK_TARGET_SIZE_VALUE; + settings->width = DEFAULT_SIZE_VALUE; + settings->height = DEFAULT_SIZE_VALUE; + return settings; +} + +int free_settings_config(settings_config *set) { + free(set); + return 0; +} + +layer_config make_layer_config() { + layer_config layer; + layer.activation = NDEF_ACTIVATION; + layer.channels = DEFAULT_FILTERS_COUNT_VALUE; + layer.output_length = DEFAULT_OUTPUT_SIZE_VALUE; + layer.input_length = DEFAULT_INPUT_SIZE_VALUE; + layer.loss = NDEF_LOSS; + layer.width = DEFAULT_FILTER_SIZE_VALUE; + layer.stride = DEFAULT_STRIDE_VALUE; + layer.type = NDEF_LAYER; + layer.width = DEFAULT_SIZE_VALUE; + layer.height = DEFAULT_SIZE_VALUE; + layer.padding = DEFAULT_PADDING_VALUE; + layer.input_depth = DEFAULT_SIZE_VALUE; + layer.input_width = DEFAULT_SIZE_VALUE; + layer.input_height = DEFAULT_SIZE_VALUE; + layer.output_width = DEFAULT_SIZE_VALUE; + layer.output_height = DEFAULT_SIZE_VALUE; + return layer; +} + +npx_option *make_npx_option(void) { + npx_option *opt = malloc(sizeof(*opt)); + opt->key = NULL; + opt->val = NULL; + opt->next = NULL; + return opt; +} + +int free_npx_option(npx_option* opt) { + npx_option *curr = NULL; + npx_option *next = opt; + while (next != NULL) { + curr = next; + next = curr->next; + free(curr->key); + free(curr->val); + free(curr); + } + return 0; +} + +weights_init_type detect_init_type(const char* val) { + if (strcmp(val, XAVIER_KEY) == 0) { + return XAVIER; + } + return NDEF_INITIALIZATION; +} + +loss_type detect_loss_type(const char* val) { + if (strcmp(val, MSQE_KEY) == 0) { + return MSQE; + } else if (strcmp(val, CROSS_ENTROPY_KEY) == 0) { + return CROSS_ENTROPY; + } + return NDEF_LOSS; +} + +int is_layer(const char* val) { + if ((strcmp(val, CONNECTED_LAYER_KEY) == 0) + || (strcmp(val, LOSS_LAYER_KEY) == 0) || (strcmp(val, CONVOLUTIONAL_LAYER_KEY) == 0)) { + return 1; + } + return 0; +} + +activation_type detect_activation_type(const char* val) { + if (strcmp(val, LINEAR_KEY) == 0) { + return LINEAR; + } else if (strcmp(val, RELU_KEY) == 0) { + return RELU; + } else if (strcmp(val, LOGISTIC_KEY) == 0) { + return LOGISTIC; + } else if (strcmp(val, TH_KEY) == 0) { + return TH; + } else if (strcmp(val, SOFTMAX_KEY) == 0) { + return SOFTMAX; + } + return NDEF_ACTIVATION; +} + +learn_type detect_learning_type(const char* val) { + if (strcmp(val, GRADIENT_DESCENT_KEY) == 0) { + return GRADIENT_DESCENT; + } + return NDEF_LEARN; +} + +layer_type detect_layer_type(const char* val) { + if (strcmp(val, LOSS_LAYER_KEY) == 0) { + return LOSS; + } else if (strcmp(val, CONNECTED_LAYER_KEY) == 0) { + return CONNECTED; + } else if (strcmp(val, CONVOLUTIONAL_LAYER_KEY) == 0) { + return CONVOLUTIONAL; + } + return NDEF_LAYER; +} + +regularization_type detect_regularization_type(const char* val) { + if (strcmp(val, L1_REGULARIZATION_KEY) == 0) { + return L1; + } else if (strcmp(val, L2_REGULARIZATION_KEY) == 0) { + return L2; + } + return NDEF_REGULARIZATION; +} + +npx_option *parse_npx(const char *buffer, unsigned long length, int *layers_count) { + int count = 0; + npx_option *head = make_npx_option(); + npx_option *curr_opt = head; + if (length > 0) { + curr_opt->key = malloc(length*sizeof(char)); + curr_opt->val = malloc(length*sizeof(char)); + } + char *line = curr_opt->key; + int offset = 0; + for (int i = 0; i < length; i++) { + char c = buffer[i]; + if(c != ' ' && c != '\t' && c != '\n') { + if (c == '=') { + line[offset] = '\0'; + line = curr_opt->val; + offset = 0; + } else { + line[offset] = c; + offset++; + } + } else if (c == '\n'){ + line[offset] = '\0'; + offset = 0; + if (length - i > 1) { + if(is_layer(curr_opt->key)) { + count++; + } + curr_opt->next = make_npx_option(); + curr_opt = curr_opt->next; + curr_opt->key = malloc(length*sizeof(char)); + curr_opt->val = malloc(length*sizeof(char)); + line = curr_opt->key; + } + } + } + if (length > 0) { + line[offset] = '\0'; + } + *layers_count = count; + return head; +} diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..b70ff97 --- /dev/null +++ b/src/config.h @@ -0,0 +1,130 @@ +// +// config.h +// netapix +// +// Created by Evgeny Dedovets on 4/20/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef config_h +#define config_h + +typedef enum { + CONVOLUTIONAL, + CONNECTED, + LOSS, + NDEF_LAYER +} layer_type; + +typedef enum { + GRADIENT_DESCENT, + NDEF_LEARN +} learn_type; + +typedef enum { + SOFTMAX, + RELU, + TH, + LOGISTIC, + LINEAR, + NDEF_ACTIVATION +} activation_type; + +typedef enum { + MSQE, + CROSS_ENTROPY, + NDEF_LOSS +} loss_type; + +typedef enum { + SOFTMAX_MODE, + SCALAR_DERIVATIVE_MODE, + NDEF_LOSS_MODE +} loss_mode; + +typedef enum { + L1, + L2, + NDEF_REGULARIZATION +} regularization_type; + +typedef enum { + XAVIER, + NDEF_INITIALIZATION +} weights_init_type; + +typedef struct npx_option { + char *key; + char *val; + struct npx_option *next; +} npx_option; + +typedef struct { + layer_type type; + int input_width; + int input_height; + int input_depth; + int output_width; + int output_height; + int input_length; + int output_length; + activation_type activation; + int width; + int height; + int padding; + int stride; + int channels; + loss_type loss; +} layer_config; + +typedef struct { + int batch; + int channels; + int width; + int height; + int threads; + learn_type learning; + regularization_type regularization; + weights_init_type init; + float eta; + float accuracy; + int validation; + int save_frequency; + float lambda; + float momentum; + float gamma; + float beta; + float alpha; + int input_length; + int target_length; +} settings_config; + +typedef struct { + layer_config *net; + settings_config *settings; + int size; +} npx_config; + +int pick_convolutional_output_size(int input_size, int filter_size, int padding, int stride); +int init_inp_out(npx_config *npx); +npx_config *read_npx(const char *filename); +int validate_npx(npx_config *npx); +int validate_settings(settings_config *config); +int validate_layer(layer_config layer, int index, int count); +npx_option *parse_npx(const char *buffer, unsigned long length, int *layers_count); +learn_type detect_learning_type(const char* val); +weights_init_type detect_init_type(const char* val); +regularization_type detect_regularization_type(const char* val); +layer_type detect_layer_type(const char* val); +activation_type detect_activation_type(const char* val); +loss_type detect_loss_type(const char* val); +int is_layer(const char* val); +npx_config* make_npx_config(void); +int free_npx_config(npx_config *npx); +settings_config *make_settings_config(void); +int free_settings_config(settings_config *set); +layer_config make_layer_config(void); +npx_option *make_npx_option(void); +int free_npx_option(npx_option* opt); + +#endif /* config_h */ diff --git a/src/connected.c b/src/connected.c new file mode 100644 index 0000000..7a096f2 --- /dev/null +++ b/src/connected.c @@ -0,0 +1,96 @@ +// +// connected.c +// netapix +// +// Created by Evgeny Dedovets on 1/11/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "connected.h" +#include "math.h" +#include +#include "utils.h" + +connected_layer *make_connected_layer(layer_config config, float *weights, float *prev_gradients, float *input_derivative, float *input, float *corrections) { + connected_layer *layer = malloc(sizeof(*layer)); + layer->type = config.type; + layer->input_derivative = input_derivative; + if (input != NULL) { + layer->input = input; + } else { + layer->input = calloc(config.input_length, sizeof(*layer->input)); + } + layer->weights = make_matrix(config.input_length, config.output_length, 1, 0, weights); + layer->biases = &weights[config.output_length * config.input_length]; + layer->output = calloc(config.output_length, sizeof(*layer->output)); + layer->output_derivative = calloc(config.output_length, sizeof(*layer->output_derivative)); + if (corrections != NULL) { + layer->corrections = make_matrix(config.input_length, config.output_length, 1, 0, corrections); + layer->biases_corrections = &corrections[config.output_length * config.input_length]; + } else { + layer->corrections = NULL; + layer->biases_corrections = NULL; + } + layer->gradients = calloc(config.output_length, sizeof(*layer->gradients)); + layer->previous_gradients = prev_gradients; + layer->activation = config.activation; + layer->input_length = config.input_length; + layer->output_length = config.output_length; + return layer; +} + +int free_connected_layer(connected_layer *layer, int is_first_layer) { + free_matrix(layer->weights, layer->input_length); + free(layer->output); + free(layer->output_derivative); + if (layer->corrections != NULL) { + free_matrix(layer->corrections, layer->input_length); + } + free(layer->gradients); + if (is_first_layer) { + free(layer->input); + } + free(layer); + return 0; +} + +void connected_forward(connected_layer *layer) { + int i, j; + for (i = 0; i < layer->output_length; i++) { + layer->output[i] = 0; + for (j = 0; j < layer->input_length; j++) { + layer->output[i] = layer->output[i] + layer->input[j] * *layer->weights[j][i]; + } + layer->output[i] = layer->output[i] + layer->biases[i]; + if (layer->activation != SOFTMAX) { + layer->output_derivative[i] = derivative(layer->output[i], layer->activation); + activate(&layer->output[i], layer->activation); + } + } + if (layer->activation == SOFTMAX) { + softmax(layer->output, layer->output_length); + } +} + +void connected_backward(connected_layer *layer) { + int i, j; + for (i = 0; i < layer->input_length; i++) { + layer->previous_gradients[i] = 0; + for (j = 0; j < layer->output_length; j++) { + layer->previous_gradients[i] = layer->previous_gradients[i] + layer->gradients[j] * *layer->weights[i][j]; + } + layer->previous_gradients[i] = layer->previous_gradients[i] * layer->input_derivative[i]; + } +} + +void calc_connected_corrections(connected_layer *layer) { + int i, j; + for(i = 0; i < layer->input_length; i++) { + for (j = 0; j < layer->output_length; j++) { + *layer->corrections[i][j] = *layer->corrections[i][j] + layer->input[i] * layer->gradients[j]; + } + } + for (i = 0; i < layer->output_length; i++) { + layer->biases_corrections[i] = layer->biases_corrections[i] + layer->gradients[i]; + } +} diff --git a/src/connected.h b/src/connected.h new file mode 100644 index 0000000..2a70ab6 --- /dev/null +++ b/src/connected.h @@ -0,0 +1,52 @@ +// +// connected.h +// netapix +// +// Created by Evgeny Dedovets on 1/11/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef connected_h +#define connected_h + +#include "config.h" + +typedef struct { + //Type of layer. + layer_type type; + //Not activated input with applied delivative of activation function from previous layer. + float *input_derivative; + //Input array of channels. + float *input; + //Weights (width = number of outputs, heights = number of inputs). + float ***weights; + //Vector of bias values (size = number of outputs). + float *biases; + //Output array of channels (size = number of outputs). + float *output; + //Output with applied derivative of activation function instead of activation itself + //(size = number of outputs). + float *output_derivative; + //Weights correction (width = number of outputs, heights = number of inputs). + float ***corrections; + //Batch bias corrections (size = number of outputs). + float *biases_corrections; + //Error gradients (size = number of outputs). + float *gradients; + //Previous layer error gradients (size = number of inputs). + float *previous_gradients; + //Activation type. + activation_type activation; + //Number of inputs. + int input_length; + //Number of outputs. + int output_length; +} connected_layer; + +connected_layer *make_connected_layer(layer_config config, float *weights, float *prev_gradients, float *input_derivative, float *input, float *corrections); +int free_connected_layer(connected_layer *layer, int is_first_layer); +void connected_forward(connected_layer *layer); +void connected_backward(connected_layer *layer); +void calc_connected_corrections(connected_layer *layer); + +#endif /* connected_h */ diff --git a/src/constants.h b/src/constants.h new file mode 100644 index 0000000..2c9e8da --- /dev/null +++ b/src/constants.h @@ -0,0 +1,165 @@ +// +// Constants.h +// Parser +// +// Created by Evgeny Dedovets on 4/22/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef Constants_h +#define Constants_h + +#define CONFIG_KEY "[config]" + +#define BATCH_KEY "batch" +#define THREADS_KEY "threads" + +#define INIT_KEY "init" +#define XAVIER_KEY "xavier" + +#define LEARNING_KEY "learning" +#define GRADIENT_DESCENT_KEY "gradient" + +#define REGULARIZATION_KEY "regularization" +#define L1_REGULARIZATION_KEY "L1" +#define L2_REGULARIZATION_KEY "L2" + +#define ETA_KEY "eta" +#define CHANNELS_KEY "channels" +#define ACCURACY_KEY "accuracy" +#define VALIDATION_KEY "validation" +#define SAVE_FREQUENCY_KEY "backup" +#define LAMBDA_KEY "lambda" +#define MOMENTUM_KEY "momentum" +#define ALPHA_KEY "alpha" +#define BETA_KEY "beta" +#define GAMMA_KEY "gamma" + +#define CONNECTED_LAYER_KEY "[connected]" +#define CONVOLUTIONAL_LAYER_KEY "[convolutional]" +#define INPUT_KEY "input" +#define STRIDE_KEY "stride" +#define PADDING_KEY "padding" +#define WIDTH_KEY "width" +#define HEIGHT_KEY "height" + +#define ACTIVATION_KEY "activation" +#define LINEAR_KEY "linear" +#define RELU_KEY "relu" +#define LOGISTIC_KEY "logistic" +#define TH_KEY "th" +#define SOFTMAX_KEY "softmax" + +#define LOSS_LAYER_KEY "[loss]" + +#define MSQE_KEY "msqe" +#define CROSS_ENTROPY_KEY "entropy" + +typedef enum { + NPX_SUCCESS = 0, + NPX_FAIL = -1 +} npx_status; + +typedef enum { + ERROR_OPEN = -1, + ERROR_FORMAT = -2 +} npx_error; + +#define DEFAULT_CHANNELS_VALUE -1 +#define DEFAULT_BATCH_VALUE 1 +#define DEFAULT_THREADS_VALUE 1 +#define DEFAULT_ETA_VALUE -1 +#define DEFAULT_ACCURACY_VALUE -1 +#define DEFAULT_VALIDATION_VALUE -1 +#define DEFAULT_GAMMA_VALUE 1 +#define DEFAULT_ALPHA_VALUE -1 +#define DEFAULT_BETA_VALUE -1 +#define DEFAULT_MOMENTUM_VALUE 0 +#define DEFAULT_LAMBDA_VALUE 0 +#define DEFAULT_NETWORK_INPUT_SIZE_VALUE -1 +#define DEFAULT_NETWORK_TARGET_SIZE_VALUE -1 +#define DEFAULT_SIZE_VALUE -1 +#define DEFAULT_FILTERS_COUNT_VALUE -1 +#define DEFAULT_INPUT_SIZE_VALUE -1 +#define DEFAULT_OUTPUT_SIZE_VALUE -1 +#define DEFAULT_FILTER_SIZE_VALUE -1 +#define DEFAULT_STRIDE_VALUE -1 +#define DEFAULT_PADDING_VALUE -1 + +#define ERROR_OPEN_FILE_MSG "Unable to open file: %s.\n" +#define ERROR_FORMAT_NOT_VALID_BATCH_MSG "Batch size should be > 0, actual value: %d.\n" +#define ERROR_FORMAT_NOT_VALID_VALIDATION_MSG "Validation value should be > 0, actual value: %d.\n" +#define ERROR_FORMAT_NOT_VALID_SAVE_FREQUENCY_MSG "Save frequency value should be > 0, actual value: %d.\n" +#define ERROR_FORMAT_NOT_VALID_THREADS_MSG "Threads count should be > 0, actual value: %d.\n" +#define ERROR_FORMAT_BATCH_LESS_THEN_THREADS_MSG "Threads count should be less or equal to batch, current threads: %d, current batch: %d.\n" +#define ERROR_FORMAT_MISSING_LEARNING_TYPE_MSG "Learning type not specified.\n" +#define ERROR_FORMAT_MISSING_ETA_MSG "Learning rate not specified.\n" +#define ERROR_FORMAT_NOT_VALID_ETA_MSG "Learning rate should be > 0, actual value: %f.\n" +#define ERROR_FORMAT_MISSING_ALPHA_MSG "Alpha not specified.\n" +#define ERROR_FORMAT_NOT_VALID_ALPHA_MSG "Alpha should be > 0 & < 1, actual value: %f.\n" +#define ERROR_FORMAT_MISSING_BETA_MSG "Beta not specified.\n" +#define ERROR_FORMAT_NOT_VALID_BETA_MSG "Beta should be > 1, actual value: %f.\n" +#define ERROR_FORMAT_MISSING_GAMMA_MSG "Gamma not specified.\n" +#define ERROR_FORMAT_NOT_VALID_GAMMA_MSG "Gamma should be > 0, actual value: %f.\n" +#define ERROR_FORMAT_NOT_VALID_MOMENTUM_MSG "Momentum should be >= 0, actual value: %f.\n" +#define ERROR_FORMAT_NOT_VALID_LAMBDA_MSG "Lambda should be > 0, actual value: %f.\n" +#define ERROR_FORMAT_MISSING_ACCURACY_MSG "Accuracy not specified.\n" +#define ERROR_FORMAT_NOT_VALID_ACCURACY_MSG "Accuracy should be > 0, actual value: %f.\n" +#define ERROR_FORMAT_MISSING_INPUT_SIZE_MSG "Input not specified for %s layer at index %d.\n" +#define ERROR_FORMAT_NOT_VALID_INPUT_SIZE_MSG "Input should be > 0 for %s layer at index %d, actual value: %d.\n" +#define ERROR_FORMAT_MISSING_ACTIVATION_MSG "Valid activation not specified for %s layer at index %d.\n" +#define ERROR_FORMAT_MISSING_FILTER_WIDTH_MSG "Filter width not specified for %s layer at index %d.\n" +#define ERROR_FORMAT_MISSING_FILTER_HEIGHT_MSG "Filter height not specified for %s layer at index %d.\n" +#define ERROR_FORMAT_NOT_VALID_FILTER_WIDTH_MSG "Filter width should be > 0 for %s layer at index %d, actual value: %d.\n" +#define ERROR_FORMAT_NOT_VALID_FILTER_HEIGHT_MSG "Filter height should be > 0 for %s layer at index %d, actual value: %d.\n" +#define ERROR_FORMAT_MISSING_FILTERS_COUNT_MSG "Filter count not specified for %s layer at index %d.\n" +#define ERROR_FORMAT_NOT_VALID_FILTERS_COUNT_MSG "Filter count should be > 0 for %s layer at index %d, actual value: %d.\n" +#define ERROR_FORMAT_MISSING_CHANNELS_MSG "Channels required for networks starts with %s layer.\n" +#define ERROR_FORMAT_MISSING_INPUT_WIDTH_MSG "Input width not specified. Required for networks starts with %s layer.\n" +#define ERROR_FORMAT_MISSING_INPUT_HEIGHT_MSG "Input height not specified. Required for networks starts with %s layer.\n" +#define ERROR_FORMAT_NOT_VALID_INPUT_WIDTH_MSG "Input width value shoule be > 0, actual value: %d.\n" +#define ERROR_FORMAT_NOT_VALID_INPUT_HEIGHT_MSG "Input height value shoule be > 0, actual value: %d.\n" +#define ERROR_FORMAT_NOT_VALID_CHANNELS_MSG "Channels value shoule be > 0, actual value: %d.\n" +#define ERROR_FORMAT_MISSING_STRIDE_MSG "Stride not specified for %s layer at index %d.\n" +#define ERROR_FORMAT_NOT_VALID_STRIDE_MSG "Stride should be > 0 for %s layer at index %d, actual value: %d.\n" +#define ERROR_FORMAT_MISSING_PADDING_MSG "Padding not specified for %s layer at index %d.\n" +#define ERROR_FORMAT_NOT_VALID_PADDING_MSG "Padding should be >= 0 for %s layer at index %d, actual value: %d.\n" +#define ERROR_FORMAT_LOSS_POSITION_MSG "%s should be at the very last position.\n" +#define ERROR_FORMAT_LAYERS_COUNT_MSG "At least one data layer should exist in network.\n" +#define ERROR_FORMAT_LAYER_TYPE_IS_NOT_CORRECT_MSG "Layer at position %d has not allowed type.\n" +#define ERROR_FORMAT_BINDING_LAYERS_MSG "Can't bind layers with positions %d and %d.\n" +#define ERROR_FORMAT_CONVOLUTIONAL_AFTER_CONNECTED_MSG "[convolutional] layer can't follow [connected].\n" +#define ERROR_FORMAT_NO_LAYERS_MSG "No layers found in the npx.\n" +#define ERROR_FORMAT_MISSING_INIT_TYPE_MSG "Missing weights initialization type.\n" +#define ERROR_FORMAT_SOFTMAX_NOT_ALLOWED_MSG "Softmax not allowed for any layer except one preventing loss.\n" +#define ERROR_FORMAT_SOFTMAX_REQUIRED_MSG "Softmax required for last computiona layer while using CROSS ENTROPY loss function.\n" + +#define ERROR_NOT_ENOUGH_DATA_MSG "Training set contains not enough items. Number of items should be more or equal to the batch size. Actual number of items: %d, actual batch size: %d.\n" + +#define FATAL_ERROR_ACTIVATION_FAIL_MSG "Ooops, something went wrong. Can't apply activation. Report to developer.\n" +#define FATAL_ERROR_LOSS_FAIL_MSG "Ooops, something went wrong. Can't apply loss. Report to developer.\n" +#define FATAL_ERROR_NDF_LEARNING_TYPE_FAIL_MSG "Ooops, something went wrong. Not defined learning type found. Report to developer.\n" +#define FATAL_ERROR_INIT_CONNECTED_WEIGHTS_FAIL_MSG "Ooops, something went wrong. Attempt to init connected weights with not defined initialization type. Report to developer.\n" +#define FATAL_ERROR_INIT_CONVOLUTIONAL_WEIGHTS_FAIL_MSG "Ooops, something went wrong. Attempt to init convolutional weights with not defined initialization type. Report to developer.\n" +#define FATAL_ERROR_MAKE_WEIGHTS_FAIL_MSG "Ooops, something went wrong. Attempt to make weights for unexpected layer type. Report to developer.\n" +#define FATAL_ERROR_INIT_WEIGHTS_FAIL_MSG "Ooops, something went wrong. Attempt to init %s weights. Report to developer.\n" +#define FATAL_ERROR_MAKE_BATCH_CORRECTIONS_FAIL_MSG "Ooops, something went wrong. Attempt to make batch corrections for unexpected layer type %s. Report to developer.\n" + +#define FATAL_ERROR_FREE_BATCH_CORRECTIONS_FAIL_MSG "Ooops, something went wrong. Attempt to free batch corrections for unexpected layer type %s. Report to developer.\n" + +#define FATAL_ERROR_FREE_WEIGHTS_FAIL_MSG "Ooops, something went wrong. Attempt to free weights for unexpected layer type %s. Report to developer.\n" +#define FATAL_ERROR_MAKE_LAYER_FAIL_MSG "Ooops, something went wrong. Attempt to make unexpected layer. Report to developer.\n" +#define FATAL_ERROR_ACCESS_NDF_LAYER_FAIL_MSG "Ooops, something went wrong. Attempt to acsess [NOT DEFINED LAYER]. Report to developer.\n" +#define FATAL_ERROR_MAKE_NETWORK_FAIL_MSG "Ooops, something went wrong. Attempt to make network failed. Unexpected layers order. Report to developer.\n" + +#define FATAL_ERROR_NPT_INPUT_FORMAT_FAIL_MSG "Input format not correct. Expected input length: %d, actual value: %d at file: %s\n" +#define FATAL_ERROR_NPT_TARGET_FORMAT_FAIL_MSG "Target format not correct. Expected target length: %d, actual value: %d at file: %s\n" + +#define FATAL_ERROR_NPW_FORMAT_FAIL_MSG "Weight file format is not correct. Undefined weights count at file: %s.\n" +#define FATAL_ERROR_NPW_VALUES_FORMAT_FAIL_MSG "Weight values format is not correct. Expected values count: %d, actual: %d at file: %s\n" +#define FATAL_ERROR_NPW_CONFIG_FORMAT_FAIL_MSG "Layer config type is not correct. Error reading at file: %s\n" +#define FATAL_ERROR_WRITE_LOSS_LAYER_NPO_FAIL_MSG "Ooops, something went wrong. Attempt to write loss layer output. Report to developer.\n" + +#define FATAL_ERROR_NPI_VALUES_FORMAT_FAIL_MSG "Input values format is not correct. Expected values count: %d, actual: %d at file: %s\n" + +#endif /* Constants_h */ diff --git a/src/convolutional.c b/src/convolutional.c new file mode 100644 index 0000000..f43d9a9 --- /dev/null +++ b/src/convolutional.c @@ -0,0 +1,159 @@ +// +// convolutional.c +// netapix +// +// Created by Evgeny Dedovets on 6/7/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "convolutional.h" +#include +#include +#include "utils.h" +#include "math.h" + +convolutional_layer *make_convolutional_layer(layer_config config, float *weights, float *prev_gradients, float *input_derivative, float *input, float *corrections) { + convolutional_layer *layer = malloc(sizeof(*layer)); + layer->p = config.padding; + layer->s = config.stride; + layer->h = config.input_height; + layer->w = config.input_width; + layer->z = config.input_depth; + layer->w1 = config.width; + layer->h1 = config.height; + layer->z1 = config.input_depth; + layer->h2 = config.output_height; + layer->w2 = config.output_width; + layer->z2 = config.channels; + layer->type = config.type; + layer->activation = config.activation; + layer->output_length = config.output_length; + if (input != NULL) { + layer->input = input; + } else { + layer->input = calloc(config.input_depth*config.input_width*config.input_height, sizeof(*layer->input)); + } + layer->input_tensor = make_tensor(config.input_height, config.input_width, config.input_depth, 0, layer->input); + if (input_derivative != NULL) { + layer->input_derivative_tensor = make_tensor(config.input_height, config.input_width, config.input_depth, 0, input_derivative); + } + layer->weights_tmp = weights; + layer->weights = make_array_of_tensors(config.height, config.width, config.input_depth, config.channels, weights); + layer->biases = &weights[config.channels*config.height*config.width*config.input_depth]; + layer->output = calloc(config.output_length, sizeof(*layer->output)); + if (corrections != NULL) { + layer->corrections = make_array_of_tensors(config.height, config.width, config.input_depth, config.channels, corrections); + layer->biases_corrections = &corrections[config.channels*config.height*config.width*config.input_depth]; + } else { + layer->corrections = NULL; + layer->biases_corrections = NULL; + } + layer->output_tensor = make_tensor(config.output_height, config.output_width, config.channels, 0, layer->output); + layer->output_derivative = calloc(config.output_length, sizeof(*layer->output)); + layer->output_derivative_tensor = make_tensor(config.output_height, config.output_width, config.channels, 0, layer->output_derivative); + if (prev_gradients != NULL) { + layer->previous_gradients_tensor = make_tensor(config.input_height, config.input_width, config.input_depth, 0, prev_gradients); + } + layer->gradients = calloc(config.output_length, sizeof(*layer->output)); + layer->gradients_tensor = make_tensor(config.output_height, config.output_width, config.channels, 0, layer->gradients); + return layer; +} + +void convolutional_forward(convolutional_layer *layer) { + int h1, w1, z1, h2, w2, z2; + for (z2 = 0; z2 < layer->z2; z2++) { + for (h2 = 0; h2 < layer->h2; h2++) { + for (w2 = 0; w2 < layer->w2; w2++) { + *layer->output_tensor[z2][h2][w2] = 0; + for (z1 = 0; z1 < layer->z1; z1++) { + for (h1 = 0; h1 < layer->h1; h1++) { + for (w1 = 0; w1 < layer->w1; w1++) { + *layer->output_tensor[z2][h2][w2] = *layer->output_tensor[z2][h2][w2] + *layer->weights[z2][z1][h1][w1] * (*layer->input_tensor[z1][h2*layer->s + h1 - layer->p][w2*layer->s + w1 - layer->p]); + } + } + } + *layer->output_tensor[z2][h2][w2] = *layer->output_tensor[z2][h2][w2] + layer->biases[z2]; + if (layer->activation != SOFTMAX) { + *layer->output_derivative_tensor[z2][h2][w2] = derivative(*layer->output_tensor[z2][h2][w2], layer->activation); + activate(layer->output_tensor[z2][h2][w2], layer->activation); + } + } + } + } + if (layer->activation == SOFTMAX) { + softmax(layer->output, layer->output_length); + } +} + +void convolutional_backward(convolutional_layer *layer) { + int h, w, z, h2, w2, z2; + double h2BottomLimit, w2BottomLimit, h2TopLimit, w2TopLimit; + for (z = 0; z < layer->z; z++) { + for (h = 0; h < layer->h; h++) { + for (w = 0; w < layer->w; w++) { + *layer->previous_gradients_tensor[z][h][w] = 0; + for (z2 = 0; z2 < layer->z2; z2++) { + h2BottomLimit = relu(ceil((double)(h + layer->p + 1 - layer->h1) / layer->s)); + h2TopLimit = floor((double)(h + layer->p) / layer->s); + if (h2TopLimit > layer->h2 - 1) { + h2TopLimit = layer->h2 - 1; + } + for (h2 = h2BottomLimit; h2 <= h2TopLimit; h2++) { + w2BottomLimit = relu(ceil((float)(w + layer->p + 1 - layer->w1) / layer->s)); + w2TopLimit = floor((double)(w + layer->p) / layer->s); + if (w2TopLimit > layer->w2 - 1) { + w2TopLimit = layer->w2 - 1; + } + for (w2 = w2BottomLimit; w2 <= w2TopLimit; w2++) { + *layer->previous_gradients_tensor[z][h][w] = *layer->previous_gradients_tensor[z][h][w] + *layer->gradients_tensor[z2][h2][w2] * *layer->weights[z2][z][h - h2 * layer->s + layer->p][w - w2 * layer->s + layer->p]; + } + } + } + *layer->previous_gradients_tensor[z][h][w] = *layer->previous_gradients_tensor[z][h][w] * *layer->input_derivative_tensor[z][h][w]; + } + } + } +} + +void calc_сonvolutional_сorrections(convolutional_layer *layer) { + int h1, w1, z1, h2, w2, z2; + for (z2 = 0; z2 < layer->z2; z2++) { + for (z1 = 0; z1 < layer->z1; z1++) { + for (h1 = 0; h1 < layer->h1; h1++) { + for (w1 = 0; w1 < layer->w1; w1++) { + for (h2 = 0; h2 < layer->h2; h2++) { + for (w2 = 0; w2 < layer->w2; w2++) { + *layer->corrections[z2][z1][h1][w1] = *layer->corrections[z2][z1][h1][w1] + *layer->gradients_tensor[z2][h2][w2] * *layer->input_tensor[z1][h2 * layer->s + h1 - layer->p][w2 * layer->s + w1 - layer->p]; + } + } + } + } + } + for (h2 = 0; h2 < layer->h2; h2++) { + for (w2 = 0; w2 < layer->w2; w2++) { + layer->biases_corrections[z2] = layer->biases_corrections[z2] + *layer->gradients_tensor[z2][h2][w2]; + } + } + } +} + +int free_convolutional_layer(convolutional_layer *layer, int is_first_layer) { + if (is_first_layer) { + free(layer->input); + free_tensor(layer->previous_gradients_tensor, layer->h, layer->z); + free_tensor(layer->input_derivative_tensor, layer->h, layer->z); + } + free_tensor(layer->input_tensor , layer->h, layer->z); + free_array_of_tensors(layer->weights, layer->h1, layer->z1, layer->z2); + free(layer->output); + if (layer->corrections != NULL) { + free_array_of_tensors(layer->corrections, layer->h1, layer->z1, layer->z2); + } + free_tensor(layer->output_tensor, layer->h2, layer->z2); + free(layer->output_derivative); + free_tensor(layer->output_derivative_tensor, layer->h2, layer->z2); + free(layer->gradients); + free_tensor(layer->gradients_tensor, layer->h2, layer->z2); + free(layer); + return 0; +} diff --git a/src/convolutional.h b/src/convolutional.h new file mode 100644 index 0000000..ef4bf2e --- /dev/null +++ b/src/convolutional.h @@ -0,0 +1,105 @@ +// +// convolutional.h +// netapix +// +// Created by Evgeny Dedovets on 6/7/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef convolutional_h +#define convolutional_h + +#include "config.h" + +/** + * Defines convolutional layer structure. + */ +typedef struct { + //Type of layer + layer_type type; + + //Input from previous layer. + float *input; + + float ****input_tensor; + + float ****input_derivative_tensor; + + float *weights_tmp; + //Array of filters at the specific convolutional layer. + float *****weights; + + float *****corrections; + + //Array of biases. + float *biases; + + //Array of biases batch corrections. + float *biases_corrections; + + //Output array of feature maps. + float *output; + + int output_length; + + float ****output_tensor; + + float *output_derivative; + + //Output tensor with applied derivative of activation function instead of activation itself. + float ****output_derivative_tensor; + + //Previous layer error gradient. + + float ****previous_gradients_tensor; + + //Array of gradients. + float *gradients; + + float ****gradients_tensor; + + //Activation type. + activation_type activation; + + //Width of input tensor. + int w; + + //Height of input tensor. + int h; + + //Depth of input tensor. + int z; + + //Width of filter tensor. + int w1; + + //Height of filter tensor. + int h1; + + //Depth of input tensor. + int z1; + + //Width of output tensor. + int w2; + + //Height of output tensor. + int h2; + + //Number of filters. + int z2; + + //Stride + int s; + + // padding + int p; + +} convolutional_layer; + +convolutional_layer *make_convolutional_layer(layer_config config, float *weights, float *prev_gradients, float *input_derivative, float *input, float *corrections); +int free_convolutional_layer(convolutional_layer *layer, int is_first_layer); +void convolutional_forward(convolutional_layer *layer); +void convolutional_backward(convolutional_layer *layer); +void calc_сonvolutional_сorrections(convolutional_layer *layer); + +#endif /* convolutional_h */ diff --git a/src/loss.c b/src/loss.c new file mode 100644 index 0000000..e6e6d3c --- /dev/null +++ b/src/loss.c @@ -0,0 +1,96 @@ +// +// loss.c +// netapix +// +// Created by Evgeny Dedovets on 1/11/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "loss.h" +#include "math.h" +#include "constants.h" +#include +#include + +loss_layer *make_loss_layer(layer_config config, float *previous_gradients, float *input, float *input_derivative, float *error, loss_mode mode) { + loss_layer *layer = malloc(sizeof(*layer)); + layer->error = error; + layer->loss = config.loss; + layer->size = config.input_length; + layer->previous_gradients = previous_gradients; + layer->input = input; + layer->input_derivative = input_derivative; + layer->type = LOSS; + layer->target = calloc(config.input_length, sizeof(*layer->target)); + layer->mode = mode; + return layer; +} + +int free_loss_layer(loss_layer *layer) { + free(layer->target); + free(layer); + return 0; +} + +int loss_forward(loss_layer *layer) { + *layer->error = 0; + switch (layer->loss) { + case MSQE: + *layer->error = msqe(layer->input, layer->target, layer->size); + break; + case CROSS_ENTROPY: + *layer->error = cross_entropy(layer->input, layer->target, layer->size); + break; + case NDEF_LOSS: + printf(FATAL_ERROR_LOSS_FAIL_MSG); + exit(1); + } + return 0; +} + +int loss_backward(loss_layer *layer) { + int i, j; + for (i = 0; i < layer->size; i++) { + layer->previous_gradients[i] = 0; + switch (layer->loss) { + case MSQE: + switch (layer->mode) { + case SCALAR_DERIVATIVE_MODE: + layer->previous_gradients[i] = layer->input_derivative[i] * (layer->input[i] - layer->target[i]); + break; + case SOFTMAX_MODE: + for (j = 0; j < layer->size; j++) { + if (j == i) { + layer->previous_gradients[i] = layer->previous_gradients[i] + layer->input[i] * (layer->input[i] - layer->target[i]) * (1 - layer->input[i]); + } else { + layer->previous_gradients[i] = layer->previous_gradients[i] + (layer->target[i] - layer->input[i]) * layer->input[i] * layer->input[j]; + } + } + break; + case NDEF_LOSS_MODE: + printf(FATAL_ERROR_LOSS_FAIL_MSG); + exit(1); + break; + } + break; + case CROSS_ENTROPY: + switch (layer->mode) { + case SOFTMAX_MODE: + layer->previous_gradients[i] = layer->input[i] - layer->target[i]; + break; + default: + printf(FATAL_ERROR_LOSS_FAIL_MSG); + exit(1); + break; + } + break; + case NDEF_LOSS: + printf(FATAL_ERROR_LOSS_FAIL_MSG); + exit(1); + break; + } + } + return 0; +} + + diff --git a/src/loss.h b/src/loss.h new file mode 100644 index 0000000..8623258 --- /dev/null +++ b/src/loss.h @@ -0,0 +1,40 @@ +// +// loss.h +// netapix +// +// Created by Evgeny Dedovets on 1/11/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef loss_h +#define loss_h + +#include "config.h" + +typedef struct { + //Type of layer + layer_type type; + //Not activated input with applied delivative of activation function from previous layer. + float *input_derivative; + //Input from previous layer. + float *input; + //Target output value. + float *target; + //Number of channels. + float size; + //Loss value. + float *error; + //Error gradient. + float *previous_gradients; + //Loss type. + loss_type loss; + + loss_mode mode; +} loss_layer; + +loss_layer *make_loss_layer(layer_config config, float *previous_gradients, float *input, float *input_derivative, float *error, loss_mode mode); +int free_loss_layer(loss_layer *layer); +int loss_forward(loss_layer *layer); +int loss_backward(loss_layer *layer); + +#endif /* loss_h */ diff --git a/src/math.c b/src/math.c new file mode 100644 index 0000000..bbf4188 --- /dev/null +++ b/src/math.c @@ -0,0 +1,186 @@ +// +// math.c +// netapix +// +// Created by Evgeny Dedovets on 12/11/17. +// Copyright © 2017 Touchlane LLC. All rights reserved. +// + +#include "math.h" +#include +#include +#include +#include "constants.h" + +const float min_log_arg = 1e-15; +const float max_exp_arg = 85.0; + +void softmax(float *neurons, int lenght) { + float denominator = 0; + int i; + float max_val = detect_max(neurons, lenght); + for (i = 0; i < lenght; i++) { + denominator = denominator + expf(min(neurons[i] - max_val, max_exp_arg)); + } + for (i = 0; i < lenght; i++) { + neurons[i] = expf(min(neurons[i] - max_val, max_exp_arg)) / denominator; + } +} + +float linear(float neuron) { + return neuron; +} + +float derivative_linear(float neuron) { + return 1; +} + +float relu(float neuron) { + if (neuron < 0) { + return 0; + } + return neuron; +} + +float derivative_relu(float neuron) { + if (neuron > 0) { + return 1; + } + return 0; +} + +float th(float neuron) { + float n = max(min(neuron, max_exp_arg), -max_exp_arg); + return (expf(n) - expf(-n)) / (expf(n) + expf(-n)); +} + +float derivative_th(float neuron) { + return (1 + th(neuron))*(1 - th(neuron)); +} + +float logistic(float neuron) { + float n = min(neuron, max_exp_arg); + return expf(n) / (1 + expf(n)); +} + +float derivative_logistic(float neuron) { + return logistic(neuron)*(1 - logistic(neuron)); +} + +float msqe(float *vector, float *target, int lenght) { + float error = 0; + float tmp = 0; + for (int i = 0; i < lenght; i++) { + tmp = target[i] - vector[i]; + error = error + tmp*tmp; + } + return error/2; +} + +float cross_entropy(float *vector, float *target, int lenght) { + float error = 0; + for (int i = 0; i < lenght; i++) { + error = error + target[i] * logf(max(vector[i], min_log_arg)); + } + return -error; +} + +void activate(float *neuron, activation_type type) { + switch (type) { + case RELU: + *neuron = relu(*neuron); + break; + case TH: + *neuron = th(*neuron); + break; + case LOGISTIC: + *neuron = logistic(*neuron); + break; + case LINEAR: + *neuron = linear(*neuron); + break; + default: + printf(FATAL_ERROR_ACTIVATION_FAIL_MSG); + exit(1); + break; + } +} + +float derivative(float neuron, activation_type type) { + float derivative = 0; + switch (type) { + case RELU: + derivative = derivative_relu(neuron); + break; + case TH: + derivative = derivative_th(neuron); + break; + case LOGISTIC: + derivative = derivative_logistic(neuron); + break; + case LINEAR: + derivative = derivative_linear(neuron); + break; + default: + printf(FATAL_ERROR_ACTIVATION_FAIL_MSG); + exit(1); + break; + } + return derivative; +} + +float sqroot(float square) { + float root = square / 3, last, diff = 1; + if (square <= 0) return 0; + do { + last = root; + root = (root + square / root) / 2; + diff = root - last; + } while (diff > MINDIFF || diff < -MINDIFF); + return root; +} + +float rand_uniform(float min, float max) { + if (max < min) { + float swap = min; + min = max; + max = swap; + } + return ((float)rand() / RAND_MAX * (max - min)) + min; +} + +int rand_int(int min, int max) { + if (max < min){ + int s = min; + min = max; + max = s; + } + int r = (rand()%(max - min + 1)) + min; + return r; +} + +float average(float *val, int lenght) { + float res = 0; + for (int i = 0; i < lenght; i++) { + res = res + val[i]; + } + return res / (float)lenght; +} + +float min(float left, float right) { + return left < right ? left : right; +} + +float max(float left, float right) { + return left > right ? left : right; +} + +float detect_max(float *val, int length) { + float max = val[0]; + for (int i = length - 1; i > 0; i--) { + if (val[i] > max) { + max = val[i]; + } + } + return max; +} diff --git a/src/math.h b/src/math.h new file mode 100644 index 0000000..76d6eb5 --- /dev/null +++ b/src/math.h @@ -0,0 +1,37 @@ +// +// math.h +// netapix +// +// Created by Evgeny Dedovets on 12/11/17. +// Copyright © 2017 Touchlane LLC. All rights reserved. +// + +#ifndef math_h +#define math_h + +#include "config.h" + +#define MINDIFF 2.25e-308 // use for convergence check + +void activate(float *neuron, activation_type type); +float derivative(float neuron, activation_type type); +float linear(float neuron); +float derivative_linear(float neuron); +float relu(float neuron); +float derivative_relu(float neuron); +float th(float neuron); +float derivative_th(float neuron); +float logistic(float neuron); +float derivative_logistic(float neuron); +void softmax(float *neurons, int lenght); +float msqe(float *vector, float *target, int lenght); +float cross_entropy(float *vector, float *target, int lenght); +float sqroot(float square); +float rand_uniform(float min, float max); +int rand_int(int min, int max); +float average(float *val, int lenght); +float min(float left, float right); +float max(float left, float right); +float detect_max(float *val, int length); + +#endif /* math_h */ diff --git a/src/run.c b/src/run.c new file mode 100644 index 0000000..4370c4b --- /dev/null +++ b/src/run.c @@ -0,0 +1,263 @@ +// +// run.c +// netapix +// +// Created by Pavel Kondrashkov on 6/5/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "run.h" +#include +#include +#include "constants.h" +#include "connected.h" +#include "loss.h" +#include "train.h" +#include "convolutional.h" + +run_root *make_run_root(layer_config *configs, int layers_count, float *input_data) { + run_root *root = malloc(sizeof(*root)); + root->input_data = input_data; + root->weights = malloc(layers_count * sizeof(*root->weights)); + int i; + for (i = 0; i < layers_count; ++i) { + root->weights[i] = make_run_weights(configs[i]); + } + root->network = make_run_network(configs, layers_count, root->weights, input_data); + return root; +} + +int free_run_root(run_root *root) { + for (int i = 0; i < root->network.count; ++i) { + free_run_weights(root->weights[i]); + } + free(root->weights); + free_run_network(root->network); + free(root); + return 0; +} + +run_weights make_run_weights(layer_config config) { + run_weights weights; + weights.type = WEIGHT_RUN_TYPE; + switch (config.type) { + case CONNECTED: + weights.count = (config.input_length + 1) * config.output_length; + weights.values = calloc(weights.count, sizeof(*weights.values)); + break; + case CONVOLUTIONAL: + weights.count = config.width * config.height * config.input_depth * config.channels + config.channels; + weights.values = calloc(weights.count, sizeof(*weights.values)); + break; + default: + printf(FATAL_ERROR_MAKE_WEIGHTS_FAIL_MSG); + exit(1); + } + return weights; +} + +int free_run_weights(run_weights weights) { + free(weights.values); + return 0; +} + +run_network make_run_network(layer_config *configs, int layers_count, run_weights *weights, float *input_data) { + run_network net; + net.count = layers_count; + net.layers = malloc((layers_count) * sizeof(*net.layers)); + float *prev_output = input_data; + for (int i = 0; i < layers_count; i++) { + switch (configs[i].type) { + case CONVOLUTIONAL: + net.layers[i] = make_convolutional_layer(configs[i], weights[i].values, NULL, NULL, prev_output, NULL); + prev_output = ((convolutional_layer*)net.layers[i])->output; + break; + case CONNECTED: + net.layers[i] = make_connected_layer(configs[i], weights[i].values, NULL, NULL, prev_output, NULL); + prev_output = ((connected_layer*)net.layers[i])->output; + break; + default: + printf(FATAL_ERROR_MAKE_NETWORK_FAIL_MSG); + exit(1); + } + } + return net; +} + +int free_run_network(run_network net) { + int i; + for (i = 0; i < net.count; ++i) { + int is_first_layer = i == 0; + free_layer(net.layers[i], is_first_layer); + } + free(net.layers); + return 0; +} + +int run(char *npi_path, char *weights_path, char *output_path) { + int input_length; + float *input_data = NULL; + if (read_npi(npi_path, &input_data, &input_length)) { + return 0; + } + + int layers_count; + layer_config *configs; + if (read_layer_configs_from_npw(weights_path, &configs, &layers_count)) { + return 0; + } + + run_root *root = make_run_root(configs, layers_count, input_data); + read_npw(weights_path, root->weights, layers_count); + + for (int i = 0; i < layers_count; i++) { + forward(root->network.layers[i]); + } + + void *last_layer = root->network.layers[layers_count - 1]; + write_layer_output(output_path, last_layer); + + free_run_root(root); + return 0; +} + +int forward(void *layer) { + layer_type *type = (layer_type *)layer; + switch (*type) { + case CONNECTED: + connected_forward((connected_layer *)layer); + break; + case LOSS: + loss_forward((loss_layer *)layer); + break; + case CONVOLUTIONAL: + convolutional_forward((convolutional_layer *)layer); + break; + default: + printf(FATAL_ERROR_ACCESS_NDF_LAYER_FAIL_MSG); + exit(1); + break; + } + return 0; +} + +int read_npi(char *path, float **input, int *input_size) { + FILE *file = fopen(path, "rb"); + if (!file) { + printf(ERROR_OPEN_FILE_MSG, path); + return ERROR_OPEN; + } + + fseek(file, 0, SEEK_END); + *input_size = (int)ftell(file) / sizeof(**input); + fseek(file, 0, SEEK_SET); + *input = malloc(*input_size * sizeof(**input)); + size_t success = fread(*input, sizeof(**input), *input_size, file); + if (success != *input_size) { + printf(FATAL_ERROR_NPI_VALUES_FORMAT_FAIL_MSG, *input_size, (int)success, path); + fclose(file); + return ERROR_FORMAT; + } + + fclose(file); + return 0; +} + +int write_npo(char *path, float *output, int output_length) { + /// BINARY MODE +// FILE *file = fopen(path, "wb"); +// if (!file) { +// printf(ERROR_OPEN_FILE_MSG, path); +// return ERROR_OPEN; +// } +// fwrite(output, sizeof(*output), output_length, file); +// fclose(file); +// + /// TEXT MODE + FILE *file = fopen(path, "w"); + if (!file) { + printf(ERROR_OPEN_FILE_MSG, path); + return ERROR_OPEN; + } + int i; + for (i = 0; i < output_length; ++i) { + fprintf(file, i == 0 ? "%f" : ", %f", output[i]); + } + fclose(file); + return 0; +} + + +int write_layer_output(char *path, void *layer) { + layer_type *type = (layer_type *)layer; + switch (*type) { + case CONNECTED: { + connected_layer *connected = (connected_layer *)layer; + write_npo(path, connected->output, connected->output_length); + } + break; + case CONVOLUTIONAL: { + convolutional_layer *convolutional = (convolutional_layer *)layer; + write_npo(path, convolutional->output, convolutional->output_length); + } + break; + case LOSS: + printf(FATAL_ERROR_WRITE_LOSS_LAYER_NPO_FAIL_MSG); + exit(1); + break; + default: + printf(FATAL_ERROR_ACCESS_NDF_LAYER_FAIL_MSG); + exit(1); + break; + } + return 0; +} + +int free_layer(void *layer, int is_first_layer) { + layer_type *type = (layer_type *)layer; + switch (*type) { + case CONNECTED: + free_connected_layer((connected_layer *)layer, is_first_layer); + break; + case LOSS: + free_loss_layer((loss_layer *)layer); + break; + case CONVOLUTIONAL: + free_convolutional_layer((convolutional_layer *)layer, is_first_layer); + break; + case NDEF_LAYER: + printf(FATAL_ERROR_ACCESS_NDF_LAYER_FAIL_MSG); + exit(1); + break; + } + return 0; +} + +int read_layer_configs_from_npw(char *path, layer_config **configs, int *layers_count) { + FILE *file = fopen(path, "rb"); + if (!file) { + printf(ERROR_OPEN_FILE_MSG, path); + return ERROR_OPEN; + } + size_t success = 0; + success = fread(layers_count, sizeof(*layers_count), 1, file); + if (success != 1) { + printf(FATAL_ERROR_NPW_FORMAT_FAIL_MSG, path); + fclose(file); + return ERROR_FORMAT; + } + *configs = malloc( (*layers_count) * sizeof(**configs)); + int i; + for (i = 0; i < *layers_count; ++i) { + success = fread(&(*configs)[i], sizeof(layer_config), 1, file); + if (success != 1) { + printf(FATAL_ERROR_NPW_CONFIG_FORMAT_FAIL_MSG, path); + fclose(file); + return ERROR_FORMAT; + } + int values_count = ((*configs)[i].input_length + 1) * (*configs)[i].output_length; + fseek(file, values_count * sizeof(float), SEEK_CUR); + } + fclose(file); + return 0; +} diff --git a/src/run.h b/src/run.h new file mode 100644 index 0000000..a1245c3 --- /dev/null +++ b/src/run.h @@ -0,0 +1,55 @@ +// +// run.h +// netapix +// +// Created by Pavel Kondrashkov on 6/5/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef run_h +#define run_h + +#include +#include "config.h" + +typedef enum { + WEIGHT_TRAIN_TYPE, + WEIGHT_RUN_TYPE +} weight_type; + +typedef struct { + void **layers; + int count; +} run_network; + +typedef struct { + weight_type type; + float *values; + int count; +} run_weights; + +typedef struct { + run_weights *weights; + run_network network; + float *input_data; +} run_root; + +run_network make_run_network(layer_config *configs, int layers_count, run_weights *weights, float *input_data); +int free_run_root(run_root *root); +run_weights make_run_weights(layer_config config); +int free_run_weights(run_weights weights); + +run_root *make_run_root(layer_config *configs, int layers_count, float *input_data); +int free_run_network(run_network net); + +int run(char *npi_path, char *weights_path, char *output_path); + +int forward(void *layer); +int read_npi(char *path, float **input, int *input_size); +int write_npo(char *path, float *output, int output_length); +int write_layer_output(char *path, void *layer); +int free_layer(void *layer, int is_first_layer); + +int read_layer_configs_from_npw(char *path, layer_config **configs, int *weights_count); + +#endif /* run_h */ diff --git a/src/train.c b/src/train.c new file mode 100644 index 0000000..1912235 --- /dev/null +++ b/src/train.c @@ -0,0 +1,707 @@ +// +// train.c +// netapix +// +// Created by Evgeny Dedovets on 4/28/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "train.h" +#include +#include +#include +#include +#include +#include + +#include "constants.h" +#include "math.h" +#include "connected.h" +#include "loss.h" +#include "run.h" +#include "convolutional.h" +#include "validation.h" + +sem_t *sem; + +void *compute_network(void *n) { + sem_wait(sem); + train_network *net = n; + int item_position = *net->iteration_number * net->batch + net->position_in_batch; + read_npt(net->dataset->training_set[*net->train_set_number]->items[item_position], + input_from_layer(net->layers[0]), + ((loss_layer *)net->layers[net->count - 1])->target, + net->input_length, net->target_length); + for (int i = 0; i < net->count; i++) { + forward(net->layers[i]); + } + backward(net->layers[net->count - 1]); + for (int i = net->count - 2; i > 0; i--) { + backward(net->layers[i]); + calc_corrections(net->layers[i]); + } + calc_corrections(net->layers[0]); + sem_post(sem); + return NULL; +} + +float reg_add(regularization_type type, float lambda, float multiplier) { + float res = 0; + switch (type) { + case L1: + res = lambda; + break; + case L2: + res = lambda * multiplier; + break; + case NDEF_REGULARIZATION: + res = 0; + break; + } + return res; +} + +void *update_weights(void *w) { + sem_wait(sem); + train_weights *weights = w; + int i, j; + float delta, gradient; + switch (weights->params->learning) { + case GRADIENT_DESCENT: + for (i = 0; i < weights->count; i++) { + gradient = 0; + for (j = 0; j < weights->params->batch; j++) { + gradient = gradient + weights->corrections[j][i]; + weights->corrections[j][i] = 0; + } + delta = weights->params->eta * (gradient + reg_add(weights->params->regularization, weights->params->lambda, weights->values[i])) + weights->params->momentum * weights->prev_delta[i]; + weights->prev_delta[i] = delta; + weights->values[i] = weights->values[i] - delta; + } + break; + case NDEF_LEARN: + printf(FATAL_ERROR_NDF_LEARNING_TYPE_FAIL_MSG); + exit(1); + break; + } + sem_post(sem); + return NULL; +} + +int backward(void *layer) { + layer_type *type = (layer_type *)layer; + switch (*type) { + case CONNECTED: + connected_backward((connected_layer *)layer); + break; + case LOSS: + loss_backward((loss_layer *)layer); + break; + case CONVOLUTIONAL: + convolutional_backward((convolutional_layer *)layer); + break; + default: + printf(FATAL_ERROR_ACCESS_NDF_LAYER_FAIL_MSG); + exit(1); + break; + } + return 0; +} + +int calc_corrections(void *layer) { + layer_type *type = (layer_type *)layer; + switch (*type) { + case CONNECTED: + calc_connected_corrections((connected_layer *)layer); + break; + case CONVOLUTIONAL: + calc_сonvolutional_сorrections((convolutional_layer *)layer); + break; + default: + printf(FATAL_ERROR_ACCESS_NDF_LAYER_FAIL_MSG); + exit(1); + break; + } + return 0; +} + +float *input_from_layer(void *layer) { + float *res = NULL; + layer_type *type = (layer_type *)layer; + switch (*type) { + case CONNECTED: + res = ((connected_layer *)layer)->input; + break; + case LOSS: + res = ((loss_layer *)layer)->input; + break; + case CONVOLUTIONAL: + res = ((convolutional_layer *)layer)->input; + break; + case NDEF_LAYER: + printf(FATAL_ERROR_ACCESS_NDF_LAYER_FAIL_MSG); + exit(1); + break; + } + return res; +} + +int train(char *npx_path, char *train_path, char *weights_path, char *output_path) { + npx_config *npx = read_npx(npx_path); + if (validate_npx(npx) < 0) { + free_npx_config(npx); + return NPX_FAIL; + } + + input_data *raw_input_data = read_train_data(train_path); + int validation_items_count = validation_size(raw_input_data->count, npx->settings->validation); + int is_data_valid = validate_data(raw_input_data, + npx->settings->input_length, + npx->settings->target_length, + npx->settings->batch, + validation_items_count); + if (is_data_valid < 0) { + free_npx_config(npx); + free_input_data(raw_input_data); + return NPX_FAIL; + } + + shuffle(raw_input_data); + data_set *dataset = prepare_data_set(raw_input_data, validation_items_count); + train_root *root = make_train_root(npx, dataset); + if (weights_path) { + read_npw(weights_path, root->weights, npx->size - 1); + } else { + init_weights(root->weights, npx); + } + validation_root *validation = make_validation_root(npx, dataset, root->weights); + + sem = sem_open("THREADS_CONTROL", O_CREAT, 0600, 0); + int train_weights_count = root->npx->size - 1; + int active_update_threads_count = (int)min(train_weights_count, root->npx->settings->threads); + int iterations_in_seen = root->data_set->training_set[0]->count / root->npx->settings->batch; + int i, j; + + int epoch = 0; + root->params->prev_error = cross_validation(validation, 0); + printf("Epoch:%d Iteration:0 Cross Validation Error:%f\n", epoch, root->params->prev_error); + if (output_path != NULL) { + char *initial_weights_path = malloc((strlen(output_path) + + strlen("0.npw") + 1) * sizeof(*initial_weights_path)); + strcpy(initial_weights_path, output_path); + strcat(initial_weights_path, "0.npw"); + write_npw(initial_weights_path, root->weights, root->npx->net, root->npx->size - 1); + free(initial_weights_path); + } + int iteration = 0; + while (root->params->prev_error > root->npx->settings->accuracy) { + root->params->train_set_number = epoch % root->data_set->count; + shuffle(root->data_set->training_set[root->params->train_set_number]); + for (i = 0; i < iterations_in_seen; i++) { + root->params->iteration_number = i; + pthread_t compute_threads[root->npx->settings->batch]; + for (j = 0; j < root->npx->settings->batch; j++) { + pthread_create(&(compute_threads[j]), NULL, compute_network, &root->networks[j]); + } + for (j = 0; j < root->npx->settings->threads; j++) { + sem_post(sem); + } + for (j = 0; j < root->npx->settings->batch; j++) { + pthread_join(compute_threads[j], NULL); + } + pthread_t update_threads[train_weights_count]; + for (j = 0; j < train_weights_count; j++) { + pthread_create(&(update_threads[j]), NULL, update_weights, &root->weights[j]); + } + for (j = 0; j < active_update_threads_count; j++) { + sem_post(sem); + } + for (j = 0; j < train_weights_count; j++) { + pthread_join(update_threads[j], NULL); + } + iteration = epoch*iterations_in_seen + i; + if (iteration != 0 && iteration % npx->settings->save_frequency == 0 && output_path != NULL) { + char buffer_path[4096]; + sprintf(buffer_path, "%s%d.npw", output_path, iteration); + write_npw(buffer_path, root->weights, root->npx->net, root->npx->size - 1); + } + printf("Iteration:%d Error:%f\n", iteration, average(root->params->batch_errors, root->npx->settings->batch)); + } + float error = cross_validation(validation, root->params->train_set_number); + epoch = epoch + 1; + printf("Epoch:%d Iteration:%d Cross Validation Error:%f\n", epoch, iteration, error); + update_params(root->params, error); + } + sem_close(sem); + free_train_root(root); + free_validation_root(validation); + free_npx_config(npx); + free_input_data(raw_input_data); + free_data_set(dataset); + return NPX_SUCCESS; +} + +int update_params(train_params *params, float error) { + switch (params->learning) { + case GRADIENT_DESCENT: + if (error - params->gamma * params->prev_error > 0) { + params->eta = params->eta * params->alpha; + } else { + params->eta = params->eta * params->beta; + } + printf("Updating train params, delta error: %f, new learning rate is %f\n", error - params->gamma * params->prev_error, params->eta); + params->prev_error = error; + break; + case NDEF_LEARN: + printf(FATAL_ERROR_NDF_LEARNING_TYPE_FAIL_MSG); + exit(1); + } + return 0; +} + +data_set *make_data_set(int sets_count, int train_data_count, int validation_data_count) { + data_set *set = malloc(sizeof(*set)); + set->count = sets_count; + set->training_set = malloc(set->count * sizeof(*set->training_set)); + set->validation_set = malloc(set->count * sizeof(*set->validation_set)); + int i; + for (i = 0; i < set->count; ++i) { + set->training_set[i] = make_input_data(train_data_count); + set->validation_set[i] = make_input_data(validation_data_count); + } + return set; +} + +int free_data_set(data_set *set) { + free(set->training_set); + free(set->validation_set); + free(set); + return 0; +} + +data_set *prepare_data_set(input_data *raw_input_data, int batch_size) { + int input_count = raw_input_data->count; + int validation_rest = input_count % batch_size; + + int validation_data_count = batch_size + validation_rest; + int train_data_count = input_count - validation_data_count; + int sets_count = input_count / batch_size; + + data_set *set = make_data_set(sets_count, train_data_count, validation_data_count); + + int i, j; + for (i = 0; i < set->count; ++i) { + int training_set_index = 0; + int validation_set_index = 0; + int offset_lower_bound = i * batch_size; + int offset_higher_bound = offset_lower_bound + batch_size; + for (j = 0; j < input_count; ++j) { + /// If the item is outside validation set bound it goes to training set + /// otherwise it goes to validation set. + if ( (j < offset_lower_bound || j >= offset_higher_bound) && training_set_index != train_data_count) { + set->training_set[i]->items[training_set_index] = raw_input_data->items[j]; + training_set_index++; + } else { + set->validation_set[i]->items[validation_set_index] = raw_input_data->items[j]; + validation_set_index++; + } + } + } + return set; +} + +train_network make_train_network(npx_config *npx, data_set *data, train_weights *weights, int position_in_batch, int *iteration_number, int *train_set_number, float *error) { + train_network net; + net.dataset = data; + net.batch = npx->settings->batch; + net.input_length = npx->settings->input_length; + net.target_length = npx->settings->target_length; + net.iteration_number = iteration_number; + net.train_set_number = train_set_number; + net.position_in_batch = position_in_batch; + net.count = npx->size; + net.layers = malloc(npx->size * sizeof(*net.layers)); + float *prev_gradients = NULL; + float *prev_output_derivative = NULL; + float *prev_output = NULL; + for (int i = 0; i < npx->size - 1; i++) { + switch (npx->net[i].type) { + case CONVOLUTIONAL: + net.layers[i] = make_convolutional_layer(npx->net[i], weights[i].values, prev_gradients, prev_output_derivative, prev_output, weights[i].corrections[position_in_batch]); + prev_gradients = ((convolutional_layer*)net.layers[i])->gradients; + prev_output_derivative = ((convolutional_layer*)net.layers[i])->output_derivative; + prev_output = ((convolutional_layer*)net.layers[i])->output; + break; + case CONNECTED: + net.layers[i] = make_connected_layer(npx->net[i], weights[i].values, prev_gradients, prev_output_derivative, prev_output, weights[i].corrections[position_in_batch]); + prev_gradients = ((connected_layer*)net.layers[i])->gradients; + prev_output_derivative = ((connected_layer*)net.layers[i])->output_derivative; + prev_output = ((connected_layer*)net.layers[i])->output; + break; + default: + printf(FATAL_ERROR_MAKE_NETWORK_FAIL_MSG); + exit(1); + } + } + loss_mode mode = detect_loss_mode(npx->net[npx->size - 2]); + net.layers[npx->size - 1] = make_loss_layer(npx->net[npx->size - 1], prev_gradients, prev_output, prev_output_derivative, error, mode); + return net; +} + +loss_mode detect_loss_mode(layer_config config) { + loss_mode mode; + switch (config.activation) { + case SOFTMAX: + mode = SOFTMAX_MODE; + break; + case NDEF_ACTIVATION: + mode = NDEF_LOSS_MODE; + break; + default: + mode = SCALAR_DERIVATIVE_MODE; + break; + } + return mode; +} + +int clean_train_network(train_network net) { + for (int i = 0; i < net.count - 1; i++) { + int is_first_layer = i == 0; + free_layer(net.layers[i], is_first_layer); + } + free_loss_layer(net.layers[net.count - 1]); + free(net.layers); + return 0; +} + +train_root *make_train_root(npx_config *npx, data_set *data_set) { + train_root *root = malloc(sizeof(*root)); + root->npx = npx; + root->data_set = data_set; + root->params = make_train_params(npx->settings); + root->weights = malloc((npx->size - 1) * sizeof(*root->weights)); + for (int i = 0; i < npx->size - 1; i++) { + root->weights[i] = make_train_weights(root->npx->net[i], root->params); + } + root->networks = malloc(npx->settings->batch * sizeof(*root->networks)); + for (int i = 0; i < npx->settings->batch; i++) { + root->networks[i] = make_train_network(npx, data_set, root->weights, i, &root->params->iteration_number, &root->params->train_set_number, &root->params->batch_errors[i]); + } + return root; +} + +int free_train_root(train_root *root) { + for (int i = 0; i < root->npx->settings->batch; i++) { + clean_train_network(root->networks[i]); + } + free(root->networks); + for (int i = 0; i < root->npx->size - 1; i++) { + clean_train_weights(root->weights[i]); + } + free(root->weights); + free_train_params(root->params); + free(root); + return 0; +} + +train_params *make_train_params(settings_config *settings) { + train_params *params = malloc(sizeof(*params)); + params->alpha = settings->alpha; + params->batch = settings->batch; + params->beta = settings->beta; + params->eta = settings->eta; + params->gamma = settings->gamma; + params->lambda = settings->lambda; + params->learning = settings->learning; + params->momentum = settings->momentum; + params->prev_error = 0; + params->iteration_number = 0; + params->train_set_number = 0; + params->regularization = settings->regularization; + params->batch_errors = calloc(settings->batch, sizeof(*params->batch_errors)); + return params; +} + +int free_train_params(train_params *params) { + free(params->batch_errors); + free(params); + return 0; +} + +train_weights make_train_weights(layer_config config, train_params *params) { + train_weights weights; + weights.type = WEIGHT_TRAIN_TYPE; + weights.params = params; + switch (config.type) { + case CONNECTED: + weights.count = (config.input_length + 1) * config.output_length; + weights.values = calloc(weights.count, sizeof(*weights.values)); + break; + case CONVOLUTIONAL: + weights.count = config.width * config.height * config.input_depth * config.channels + config.channels; + weights.values = calloc(weights.count, sizeof(*weights.values)); + break; + default: + printf(FATAL_ERROR_MAKE_WEIGHTS_FAIL_MSG); + exit(1); + break; + } + weights.values = calloc(weights.count, sizeof(*weights.values)); + weights.prev_delta = calloc(weights.count, sizeof(*weights.prev_delta)); + weights.corrections = malloc(weights.params->batch * sizeof(*weights.corrections)); + for (int i = 0; i < weights.params->batch; i++) { + weights.corrections[i] = calloc(weights.count, sizeof(**weights.corrections)); + } + return weights; +} + +int clean_train_weights(train_weights weights) { + for (int i = 0; i < weights.params->batch; i++) { + free(weights.corrections[i]); + } + free(weights.corrections); + free(weights.values); + return 0; +} + +int init_weights(train_weights *weights, npx_config *npx) { + for (int i = 0; i < npx->size - 1; i++) { + switch (npx->net[i].type) { + case CONNECTED: + switch (npx->settings->init) { + case XAVIER: + xavier_init_connected_weights(weights[i].values, npx->net[i]); + break; + case NDEF_INITIALIZATION: + printf(FATAL_ERROR_INIT_CONNECTED_WEIGHTS_FAIL_MSG); + exit(1); + break; + } + break; + case CONVOLUTIONAL: + switch (npx->settings->init) { + case XAVIER: + xavier_init_convolutional_weights(weights[i].values, npx->net[i]); + break; + case NDEF_INITIALIZATION: + printf(FATAL_ERROR_INIT_CONVOLUTIONAL_WEIGHTS_FAIL_MSG); + exit(1); + break; + } + break; + case LOSS: + printf(FATAL_ERROR_INIT_WEIGHTS_FAIL_MSG, LOSS_LAYER_KEY); + exit(1); + break; + case NDEF_LAYER: + printf(FATAL_ERROR_INIT_WEIGHTS_FAIL_MSG, "[NOT DEFINED]"); + exit(1); + break; + } + } + return 0; +} + +int validation_size(int raw_input_data_size, int validation) { + int train_data_count = raw_input_data_size / validation; + int size = train_data_count > 0 ? train_data_count : 1; + return size; +} + +int xavier_init_connected_weights(float *weights, layer_config config) { + float r = sqroot( 1.0 / (config.input_length + config.output_length)); + int length = config.input_length * config.output_length; + for (int i = 0; i < length; i++) { + weights[i] = rand_uniform(-r, r); + } + return 0; +} + +int xavier_init_convolutional_weights(float *weights, layer_config config) { + int input = config.input_width * config.input_height * config.input_depth; + int output = config.output_width * config.output_height * config.channels; + float r = sqroot( 6.0 / (input + output) ); + int length = config.width * config.height * config.input_depth * config.channels; + for (int i = 0; i < length; ++i) { + weights[i] = rand_uniform(-r, r); + } + return 0; +} + +int validate_data(input_data *data, int input_length, int target_length, int batch_size, int validation_items_count) { + if ( (data->count - validation_items_count) / batch_size == 0) { + printf(ERROR_NOT_ENOUGH_DATA_MSG, data->count, batch_size); + return ERROR_FORMAT; + } + float *input = malloc(input_length * sizeof(*input)); + float *target = malloc(target_length * sizeof(*target)); + for (int i = 0; i< data->count; i++) { + if (read_npt(data->items[i], input, target, input_length, target_length) < 0) { + free(input); + free(target); + return ERROR_FORMAT; + } + } + free(input); + free(target); + return 0; +} + +int read_npt(char *path, float *input, float *target, int input_length, int target_length) { + FILE *file = fopen(path, "rb"); + if (!file) { + printf(ERROR_OPEN_FILE_MSG, path); + return ERROR_OPEN; + } + size_t success = fread(input, sizeof(*input), input_length, file); + if (success != input_length) { + printf(FATAL_ERROR_NPT_INPUT_FORMAT_FAIL_MSG, input_length, (int)success, path); + return ERROR_FORMAT; + } + + //TODO: Remove this shit + //------------------------------ + + for (int i = 0; i < input_length; i++) { + input[i] = input[i] / 255.0; + } + + //------------------------------ + + success = fread(target, sizeof(*target), target_length, file); + if (success != target_length) { + printf(FATAL_ERROR_NPT_TARGET_FORMAT_FAIL_MSG, target_length, (int)success, path); + return ERROR_FORMAT; + } + fclose(file); + return 0; +} + +input_data *read_train_data(char *path) { + int npt_count = number_of_items_at_path(path); + if (npt_count <= 0) { + return make_input_data(0); + } + input_data *data = make_input_data(npt_count); + + DIR *train_dir = opendir(path); + struct dirent *directory; + int index = 0; + while((directory = readdir(train_dir)) != NULL) { + char *ext = strrchr(directory->d_name, '.'); + if (strcmp(ext, ".npt") == 0) { + char item_path[255]; + sprintf(item_path, "%s/%s", path, directory->d_name); + data->items[index] = malloc((strlen(item_path) + 1) * sizeof(*data->items[index])); + strcpy(data->items[index], item_path); + index++; + } + } + closedir(train_dir); + return data; +} + +int shuffle(input_data *data) { + int limit = data->count - 1; + int rand_index = 0; + char *tmp = NULL; + for (int i = 0; i < data->count - 1; i++) { + rand_index = rand_int(i, limit); + tmp = data->items[i]; + data->items[i] = data->items[rand_index]; + data->items[rand_index] = tmp; + } + return 0; +} + +input_data *make_input_data(int items_count) { + input_data *data = malloc(sizeof(*data)); + data->count = items_count; + data->items = malloc(items_count * sizeof(*data->items)); + return data; +} + +int free_input_data(input_data *data) { + for (int i = 0; i < data->count; i++) { + free(data->items[i]); + } + free(data->items); + free(data); + return 0; +} + +int number_of_items_at_path(char *path) { + int count = 0; + DIR *trainDir = opendir(path); + if (trainDir == NULL) { + return count; + } + struct dirent *directory; + while((directory = readdir(trainDir)) != NULL) { + char *ext = strrchr(directory->d_name, '.'); + if (strcmp(ext, ".npt") == 0) { + count++; + } + } + closedir(trainDir); + return count; +} + +int write_npw(char *path, train_weights *weights, layer_config *configs, int weights_count) { + FILE *file = fopen(path, "wb"); + if (!file) { + printf(ERROR_OPEN_FILE_MSG, path); + return ERROR_OPEN; + } + fwrite(&weights_count, sizeof(weights_count), 1, file); + int i; + for (i = 0; i < weights_count; ++i) { + train_weights weight = weights[i]; + layer_config config = configs[i]; + fwrite(&config, sizeof(config), 1, file); + fwrite(weight.values, sizeof(*weight.values), weight.count, file); + } + + fclose(file); + return 0; +} + +int read_npw(char *path, void *weights, int weights_count) { + FILE *file = fopen(path, "rb"); + if (!file) { + printf(ERROR_OPEN_FILE_MSG, path); + return ERROR_OPEN; + } + + fseek(file, sizeof(int), SEEK_CUR); + weight_type type = *(weight_type*)weights; + int i; + size_t success = 0; + for (i = 0; i < weights_count; ++i) { + fseek(file, sizeof(layer_config), SEEK_CUR); + int weight_values_count = 0; + float *weights_values; + switch (type) { + case WEIGHT_TRAIN_TYPE: { + train_weights weight = ((train_weights *)weights)[i]; + weights_values = weight.values; + weight_values_count = weight.count; + } + break; + case WEIGHT_RUN_TYPE: { + run_weights weight = ((run_weights *)weights)[i]; + weights_values = weight.values; + weight_values_count = weight.count; + } + break; + } + success = fread(weights_values, sizeof(*weights_values), weight_values_count, file); + if (success != weight_values_count) { + printf(FATAL_ERROR_NPW_VALUES_FORMAT_FAIL_MSG, weight_values_count, (int)success, path); + fclose(file); + return ERROR_FORMAT; + } + } + fclose(file); + return 0; +} diff --git a/src/train.h b/src/train.h new file mode 100644 index 0000000..b22c50a --- /dev/null +++ b/src/train.h @@ -0,0 +1,120 @@ +// +// train.h +// netapix +// +// Created by Evgeny Dedovets on 4/28/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef train_h +#define train_h + +#include "run.h" + +typedef struct { + float eta; + float alpha; + float beta; + float momentum; + float lambda; + float gamma; + regularization_type regularization; + learn_type learning; + /// Errors array size of batchSize. Each element belongs to separate loss layer. + float *batch_errors; + float prev_error; + int batch; + int iteration_number; + int train_set_number; +} train_params; + +typedef struct { + char **items; + int count; +} input_data; + +typedef struct { + input_data **validation_set; + input_data **training_set; + int count; +} data_set; + +typedef struct { + /// Reference to the 2D array of layers with height = batchSize, width = layersCount. + void **layers; + data_set *dataset; + int position_in_batch; + int *train_set_number; + int *iteration_number; + int batch; + int input_length; + int target_length; + int count; +} train_network; + +typedef struct { + weight_type type; + float *values; + float *prev_delta; + float **corrections; + train_params *params; + int count; +} train_weights; + +typedef struct { + /// Array of batch weights struct with size = weightsCount. + train_weights *weights; + train_network *networks; + /// Pointer to a train params. + train_params *params; + npx_config *npx; + data_set *data_set; +} train_root; + +train_network make_train_network(npx_config *npx, data_set *data, train_weights *weights, int position_in_batch, int *iteration_number, int *train_set_number, float *error); +int clean_train_network(train_network net); + +int init_weights(train_weights *weights, npx_config *npx); +int xavier_init_connected_weights(float *weights, layer_config config); +int xavier_init_convolutional_weights(float *weights, layer_config config); + +int train(char *npx_path, char *train_path, char *weights_path, char *output_path); + +void *compute_network(void *n); +void *update_weights(void *w); +float *input_from_layer(void *layer); +int backward(void *layer); +int calc_corrections(void *layer); +float reg_add(regularization_type type, float lambda, float multiplier); +int update_params(train_params *params, float error); + +data_set *make_data_set(int sets_count, int train_data_count, int validation_data_count); +int free_data_set(data_set *set); +data_set *prepare_data_set(input_data *raw_input_data, int validation_data_count); + +train_params *make_train_params(settings_config *settings); +int free_train_params(train_params *params); + +train_root *make_train_root(npx_config *npx, data_set *data); +int free_train_root(train_root *root); + +train_weights make_train_weights(layer_config config, train_params *params); +int clean_train_weights(train_weights weights); + +int read_npt(char *path, float *input, float *target, int input_length, int target_length); +int shuffle(input_data *data); +int validate_data(input_data *data, int input_length, int target_length, int batch_size, int validation_count); +int number_of_items_at_path(char *path); +input_data *read_train_data(char *path); +input_data *make_input_data(int items_count); +int free_input_data(input_data *data); + +int validation_size(int raw_input_data_size, int validation_percentage); + +int compare_layer_config(layer_config l, layer_config r); +int write_npw(char *path, train_weights *weights, layer_config *configs, int weights_count); +int read_npw(char *path, void *weights, int weights_count); + +loss_mode detect_loss_mode(layer_config config); + +#endif /* train_h */ diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..de1af0e --- /dev/null +++ b/src/utils.c @@ -0,0 +1,85 @@ +// +// utils.c +// netapix +// +// Created by Evgeny Dedovets on 4/23/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "utils.h" +#include +#include + +int read_txt(const char *filename, char **buffer, unsigned long *length) { + FILE *file = fopen(filename, "rb"); + if (!file) { + printf(ERROR_OPEN_FILE_MSG, filename); + return ERROR_OPEN; + } + fseek(file, 0, SEEK_END); + *length = ftell(file); + fseek(file, 0, SEEK_SET); + *buffer = malloc(sizeof(char) * (*length)); + fread(*buffer, *length, sizeof(char), file); + fclose(file); + return 0; +} + + +float ***make_matrix(int r, int c, int stride, int offset, float *val) { + float ***m = malloc(r * sizeof(*m)); + int i, j; + for (i = 0; i < r; i++) { + m[i] = malloc(c * sizeof(**m)); + for (j = 0; j < c; j++) { + m[i][j] = &val[stride * (j + i * c) + offset]; + } + } + return m; +} + +int free_matrix(float ***m, int r) { + int i; + for (i = 0; i < r; i++) { + free(m[i]); + } + free(m); + return 0; +} + +float ****make_tensor(int r, int c, int d, int offset, float *val) { + float ****t = malloc(d * sizeof(*t)); + int i; + for (i = 0; i < d; i++) { + float ***matrix = make_matrix(r, c, d, i + offset * r * c * d, val); + t[i] = matrix; + } + return t; +} + +int free_tensor(float ****t, int r, int d) { + int i; + for (i = 0; i < d; i++) { + free_matrix(t[i], r); + } + free(t); + return 0; +} + +float *****make_array_of_tensors(int r, int c, int d, int l, float *val) { + float *****a = malloc(l * sizeof(*a)); + int i; + for (i = 0; i < l; i++) { + a[i] = make_tensor(r, c, d, i, val); + } + return a; +} + +int free_array_of_tensors(float *****a, int r, int d, int l) { + int i; + for (i = 0; i < l; i++) { + free_tensor(a[i], r, d); + } + free(a); + return 0; +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..33065d8 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,23 @@ +// +// utils.h +// netapix +// +// Created by Evgeny Dedovets on 4/23/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef utils_h +#define utils_h + +#include +#include "constants.h" + +int read_txt(const char *filename, char **buffer, unsigned long *length); +float *****make_array_of_tensors(int r, int c, int d, int l, float *val); +int free_array_of_tensors(float *****a, int r, int d, int l); +float ****make_tensor(int r, int c, int d, int offset, float *val); +int free_tensor(float ****t, int r, int d); +float ***make_matrix(int r, int c, int stride, int offset, float *val); +int free_matrix(float ***m, int r); + +#endif /* utils_h */ diff --git a/src/validation.c b/src/validation.c new file mode 100644 index 0000000..89d65b2 --- /dev/null +++ b/src/validation.c @@ -0,0 +1,143 @@ +// +// validation.c +// netapix +// +// Created by Pavel Kondrashkov on 6/11/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "validation.h" +#include +#include +#include "math.h" +#include "constants.h" +#include "loss.h" +#include "connected.h" +#include "convolutional.h" +#include "train.h" + +void *compute_validation_network(void *n) { + validation_network *net = n; + for (int i = 0; i < net->validation_items_count; ++i) { + read_npt(net->validation_data->items[net->validation_offset + i], + input_from_layer(net->layers[0]), + ((loss_layer *) net->layers[net->count - 1])->target, + net->input_length, + net->target_length); + + for (int j = 0; j < net->count; j++) { + forward(net->layers[j]); + } + (*net->batch_errors_ref)[net->validation_offset + i] = *net->error; + } + return NULL; +} + +float cross_validation(validation_root *root, int iteration) { + int validations_count = root->dataset->validation_set[iteration]->count; + int max_concurrent_threads = root->networks_count; + set_validation_offset(validations_count, root->networks, root->networks_count); + + pthread_t validate_threads[validations_count]; + for (int i = 0; i < max_concurrent_threads; i++) { + root->networks[i].validation_data = root->dataset->validation_set[iteration]; + pthread_create(&(validate_threads[i]), NULL, compute_validation_network, &root->networks[i]); + } + for (int i = 0; i < max_concurrent_threads; i++) { + pthread_join(validate_threads[i], NULL); + } + + float error = average(root->batch_errors, validations_count); + return error; +} + +validation_root *make_validation_root(npx_config *npx, data_set *dataset, train_weights *weights) { + validation_root *root = malloc(sizeof(*root)); + root->dataset = dataset; + root->npx = npx; + int validations_count = dataset->validation_set[0]->count; + root->batch_errors = malloc(validations_count * sizeof(*root->batch_errors)); + + root->networks_count = (int) min(validations_count, npx->settings->threads); + validation_network *networks = malloc(root->networks_count * sizeof(*networks)); + + for (int i = 0; i < root->networks_count; ++i) { + networks[i] = make_validation_network(npx, weights, &root->batch_errors); + } + + root->networks = networks; + return root; +} + +int free_validation_root(validation_root *root) { + for (int i = 0; i < root->networks_count; ++i) { + free_validation_network(root->networks[i]); + } + free(root->networks); + free(root->batch_errors); + free(root); + return 0; +} + +validation_network make_validation_network(npx_config *npx, train_weights *weights, float **batch_error_ref) { + validation_network net; + net.validation_data = NULL; + net.validation_items_count = 0; + net.input_length = npx->settings->input_length; + net.target_length = npx->settings->target_length; + net.error = calloc(1, sizeof(*net.error)); + net.batch_errors_ref = batch_error_ref; + net.count = npx->size; + + net.layers = malloc(npx->size * sizeof(*net.layers)); + float *prev_output = NULL; + for (int i = 0; i < npx->size - 1; i++) { + switch (npx->net[i].type) { + case CONVOLUTIONAL: + net.layers[i] = make_convolutional_layer(npx->net[i], + weights[i].values, + NULL, + NULL, + prev_output, + NULL); + prev_output = ((convolutional_layer*)net.layers[i])->output; + break; + case CONNECTED: + net.layers[i] = make_connected_layer(npx->net[i], + weights[i].values, + NULL, + NULL, + prev_output, + NULL); + prev_output = ((connected_layer*)net.layers[i])->output; + break; + default: + printf(FATAL_ERROR_MAKE_NETWORK_FAIL_MSG); + exit(1); + } + } + loss_mode mode = detect_loss_mode(npx->net[npx->size - 2]); + net.layers[npx->size - 1] = make_loss_layer(npx->net[npx->size - 1], NULL, prev_output, NULL, net.error, mode); + return net; +} + +int free_validation_network(validation_network net) { + for (int i = 0; i < net.count - 1; ++i) { + int is_first_layer = i == 0; + free_layer(net.layers[i], is_first_layer); + } + free_loss_layer(net.layers[net.count - 1]); + free(net.error); + free(net.layers); + return 0; +} + +void set_validation_offset(int validation_data_count, validation_network *networks, int networks_count) { + int items_for_validation = validation_data_count / networks_count; + int rest = validation_data_count % networks_count; + for (int i = 0; i < networks_count; i++) { + networks[i].validation_items_count = items_for_validation; + networks[i].validation_offset = items_for_validation * i; + } + networks[networks_count - 1].validation_items_count += rest; +} diff --git a/src/validation.h b/src/validation.h new file mode 100644 index 0000000..a6389de --- /dev/null +++ b/src/validation.h @@ -0,0 +1,46 @@ +// +// validation.h +// netapix +// +// Created by Pavel Kondrashkov on 6/11/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef validation_h +#define validation_h + +#include +#include "run.h" +#include "train.h" + +typedef struct { + void **layers; + int input_length; + int target_length; + int count; + input_data *validation_data; + int validation_offset; + int validation_items_count; + float *error; + float **batch_errors_ref; +} validation_network; + +typedef struct { + validation_network *networks; + int networks_count; + data_set *dataset; + npx_config *npx; + float *batch_errors; +} validation_root; + +float cross_validation(validation_root *root, int iteration); + +validation_root *make_validation_root(npx_config *npx, data_set *dataset, train_weights *weights); +int free_validation_root(validation_root *root); + +validation_network make_validation_network(npx_config *npx, train_weights *weights, float **batch_error_ref); +int free_validation_network(validation_network net); + +void set_validation_offset(int validation_data_count, validation_network *networks, int networks_count); + +#endif /* validation_h */ diff --git a/tests/config.mk b/tests/config.mk new file mode 100644 index 0000000..17ba95b --- /dev/null +++ b/tests/config.mk @@ -0,0 +1,36 @@ +-include makefile + +# Source, Executable, Library Defines. +LIB_SRC_DIR = src +LIB_TEST_DIR = tests/src +LIB_SRC := $(subst $(LIB_SRC_DIR)/,, $(shell find $(LIB_SRC_DIR) -maxdepth 1 -name '*.c')) +TEST_SRC := $(subst $(LIB_TEST_DIR)/,, $(shell find $(LIB_TEST_DIR) -name '*.c')) +TEST_EXEC_PATH = tests/bin +TEST_EXEC = $(TEST_EXEC_PATH)/test + +# Compiler, Include, Linker Defines. +CC = gcc +LIB_INCLUDE = -I./include/ -I./src/ +LIB_CFTEST = $(LIB_INCLUDE) -w -O0 -std=c99 -o $(TEST_EXEC) + +# Create a test running Executable. +lib_test: lib_test_mkdir + $(CC) $(LIB_CFTEST) $(addprefix $(LIB_TEST_DIR)/, $(TEST_SRC)) $(addprefix $(LIB_SRC_DIR)/, $(LIB_SRC)) + ./tests/bin/test + +# Create a test running Executable with coverage turned on. +lib_test_coverage: lib_test_mkdir + $(CC) $(LIB_CFTEST) -coverage $(addprefix $(LIB_TEST_DIR)/, $(TEST_SRC)) $(addprefix $(LIB_SRC_DIR)/, $(LIB_SRC)) + @rm -rf $(TEST_SRC:.c=.gcda) $(TEST_SRC:.c=.gcno) + ./tests/bin/test + +# Create obj directory for bin file. +lib_test_mkdir: + mkdir -p $(TEST_EXEC_PATH) + +# Clean Up Exectuable, Objects, Library, Coverage files +lib_test_clean: + rm -rf $(TEST_EXEC_PATH) + rm -rf $(TEST_SRC:.c=.gcda) $(TEST_SRC:.c=.gcno) + +.PHONY: lib_test lib_test_coverage lib_test_clean diff --git a/tests/src/test.c b/tests/src/test.c new file mode 100644 index 0000000..4773351 --- /dev/null +++ b/tests/src/test.c @@ -0,0 +1,79 @@ +// +// test.c +// netapix +// +// Created by Pavel Kondrashkov on 5/22/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include +#include "unit_test.h" + +#include "test_utils.h" +#include "test_math.h" +#include "test_connected.h" +#include "test_loss.h" +#include "test_training.h" +#include "test_run.h" +#include "test_validation.h" + +int main() { + test(test_utils_make_matrix, "utils test make matrix from a vector."); + test(test_utils_make_tensor, "utils test make tensor from a vector."); + test(test_utils_make_array_of_tensors, "utils test make array of tensor from a vector."); + + test(test_math_linear, "math test linear activation"); + test(test_math_derivative_linear, "math test linear activation derivative."); + test(test_math_relu, "math test relu activation."); + test(test_math_derivative_relu, "math test relu activation derivative."); + test(test_math_th, "math test th activation."); + test(test_math_derivative_th, "math test th activation derivative."); + test(test_math_logistic, "math test logistic activation."); + test(test_math_derivative_logistic, "math test logistic activation derivative."); + test(test_math_softmax, "math test softmax activation function."); + test(test_math_msqe, "math test msqe loss function."); + test(test_math_cross_entropy, "math test cross entropy loss function."); + test(test_math_sqroot, "math test square root function."); + test(test_math_activate, "math test activate switch function."); + test(test_math_derivative, "math test derivative switch function."); + test(test_math_average, "math test average function."); + test(test_math_min, "math test min function."); + test(test_math_max, "math test max function."); + + test(test_connected_make_connected_layer, "connected test make connected layer."); + test(test_connected_forward, "connected test connected forward."); + test(test_connected_backward, "connected test connected backward."); + test(test_connected_calc_corrections, "connected test connected calc corrections."); + test(test_connected_free_layer_first_layer, "connected test free layer as the first layer."); + test(test_connected_free_layer_no_corrections, "connected test free layer with no corrections set."); + + test(test_loss_make_loss_layer, "loss test make loss layer."); + test(test_loss_forward, "loss test loss forward."); + test(test_loss_backward_scalar_msqe, "loss test loss backward scalar msqe."); + test(test_loss_backward_softmax_msqe, "loss test loss backward softmax msqe."); + test(test_loss_backward_softmax_cross_entropy, "loss test loss backward softmax cross entropy."); + + test(test_run_make_run_root, "run test make run root."); + test(test_run_free_run_root, "run test free run root."); + test(test_run_make_run_weights, "run test make run weights."); + test(test_run_free_run_weights, "run test free run weights."); + test(test_run_make_network, "run test make run network."); + test(test_run_free_network, "run test free run network."); + test(test_run_free_layer, "run test free layer."); + + test(test_training_validation_data_size, "training test validation data size from input data."); + test(test_training_validation_data_size_min, "training test validation data size from input data min value."); + test(test_training_prepare_data_set, "training test prepare data set from input data."); + test(test_training_make_data_set, "training test make data set network."); + test(test_training_free_data_set, "training test free data set network."); + + test(test_validation_make_validation_root, "validation test make validation root."); + test(test_validation_free_validation_root, "validation test free validation root."); + test(test_validation_make_validation_network, "validation test make validation network."); + test(test_validation_free_validation_network, "validation test free validation network."); + test(test_validation_set_validation_offset, "validation test set validation offset."); + + + printf("PASSED: %d\nFAILED: %d\n", test_passed, test_failed); + return (test_failed > 0); +} diff --git a/tests/src/test_connected.c b/tests/src/test_connected.c new file mode 100644 index 0000000..e3fa9b4 --- /dev/null +++ b/tests/src/test_connected.c @@ -0,0 +1,252 @@ +// +// test_connected.c +// test +// +// Created by Pavel Kondrashkov on 5/25/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "test_connected.h" +#include +#include "unit_test.h" +#include "config.h" +#include "connected.h" +#include "utils.h" + +layer_config stub_connected_config(void); + +int test_connected_make_connected_layer(void) { + layer_config config = stub_connected_config(); + float weights[] = { + 0.1, 0.2, 0.3, 0.4, + 0.6, 0.3 + }; + float corrections[] = { + 0.1, 0.1, 0.4, 0.1, + 0.2, 0.1 + }; + float input[] = { 0.25, -0.2 }; + float *previous_gradient = calloc(config.output_length, sizeof(*previous_gradient)); + float *input_derivative = calloc(config.input_length, sizeof(*input_derivative)); + connected_layer *layer = make_connected_layer(config, + weights, + previous_gradient, + input_derivative, + input, + corrections); + assert_equal_float(layer->input[0], input[0]); + assert_equal_float(layer->input[1], input[1]); + assert_equal_ptr(layer->input, input); + assert_equal_ptr(layer->input_derivative, input_derivative); + + assert_equal_float(*layer->weights[0][0], weights[0]); + assert_equal_float(*layer->weights[0][1], weights[1]); + assert_equal_float(*layer->weights[1][0], weights[2]); + assert_equal_float(*layer->weights[1][1], weights[3]); + assert_equal_float(layer->biases[0], weights[4]); + assert_equal_float(layer->biases[1], weights[5]); + + assert_equal_float(layer->output[0], 0); + assert_equal_float(layer->output[1], 0); + assert_equal_float(layer->output_derivative[0], 0); + assert_equal_float(layer->output_derivative[1], 0); + + assert_equal_float(*layer->corrections[0][0], corrections[0]); + assert_equal_float(*layer->corrections[0][1], corrections[1]); + assert_equal_float(*layer->corrections[1][0], corrections[2]); + assert_equal_float(*layer->corrections[1][1], corrections[3]); + assert_equal_float(layer->biases_corrections[0], corrections[4]); + assert_equal_float(layer->biases_corrections[1], corrections[5]); + + assert_equal_float(layer->gradients[0], 0); + assert_equal_float(layer->gradients[1], 0); + + assert_equal_ptr(layer->previous_gradients, previous_gradient); + + assert_equal(layer->type, CONNECTED); + assert_equal(layer->activation, config.activation); + assert_not_equal(layer->activation, NDEF_ACTIVATION); + assert_equal(layer->input_length, config.input_length); + assert_equal(layer->output_length, config.output_length); + + free_connected_layer(layer, 0); + free(previous_gradient); + free(input_derivative); + return 0; +} + +int test_connected_forward(void) { + layer_config config = stub_connected_config(); + float weights[] = { + 0.1, 0.2, 0.3, 0.4, + 0.6, 0.3 + }; + float corrections[] = { + 0.1, 0.1, 0.4, 0.1, + 0.2, 0.1 + }; + float input[] = { 0.25, -0.2 }; + float *previous_gradient = calloc(config.output_length, sizeof(*previous_gradient)); + float *input_derivative = calloc(config.input_length, sizeof(*input_derivative)); + + connected_layer *layer = make_connected_layer(config, + weights, + previous_gradient, + input_derivative, + input, + corrections); + + connected_forward(layer); + + assert_equal_float(layer->output[0], 0.565); + assert_equal_float(layer->output[1], 0.27); + assert_equal_float(layer->output_derivative[0], 1); + assert_equal_float(layer->output_derivative[1], 1); + + free_connected_layer(layer, 0); + free(previous_gradient); + free(input_derivative); + + return 0; +} + +int test_connected_backward(void) { + layer_config config = stub_connected_config(); + float weights[] = { + 0.1, 0.2, 0.3, 0.4, + 0.6, 0.3 + }; + float corrections[] = { + 0.1, 0.1, 0.4, 0.1, + 0.2, 0.1 + }; + float input[] = { 0, -0 }; + float *previous_gradient = calloc(config.output_length, sizeof(*previous_gradient)); + float *input_derivative = calloc(config.input_length, sizeof(*input_derivative)); + input_derivative[0] = 0.3; + input_derivative[1] = 0.6; + connected_layer *layer = make_connected_layer(config, + weights, + previous_gradient, + input_derivative, + input, + corrections); + layer->gradients[0] = 0.2; + layer->gradients[1] = -0.2; + + connected_backward(layer); + + assert_equal_float(layer->previous_gradients[0], -0.006); + assert_equal_float(layer->previous_gradients[1], -0.012); + + free_connected_layer(layer, 0); + free(previous_gradient); + free(input_derivative); + + return 0; +} + +int test_connected_calc_corrections(void) { + layer_config config = stub_connected_config(); + float weights[] = { + 0.1, 0.2, 0.3, 0.4, + 0.6, 0.3 + }; + float corrections[] = { + 0.1, 0.1, 0.4, 0.1, + 0.2, 0.1 + }; + float input[] = { 0.25, -0.2 }; + float *previous_gradient = calloc(config.output_length, sizeof(*previous_gradient)); + float *input_derivative = calloc(config.input_length, sizeof(*input_derivative)); + connected_layer *layer = make_connected_layer(config, + weights, + previous_gradient, + input_derivative, + input, + corrections); + layer->gradients[0] = 0.2; + layer->gradients[1] = -0.2; + + calc_connected_corrections(layer); + + assert_equal_float(*layer->corrections[0][0], 0.15); + assert_equal_float(*layer->corrections[0][1], 0.05); + assert_equal_float(*layer->corrections[1][0], 0.36); + assert_equal_float(*layer->corrections[1][1], 0.14); + assert_equal_float(layer->biases_corrections[0], 0.4); + assert_equal_float(layer->biases_corrections[1], -0.1); + + free_connected_layer(layer, 0); + free(previous_gradient); + free(input_derivative); + + return 0; +} + +int test_connected_free_layer_first_layer(void) { + layer_config config = stub_connected_config(); + float weights[] = { + 0.1, 0.2, 0.3, 0.4, + 0.6, 0.3 + }; + float corrections[] = { + 0.1, 0.1, 0.4, 0.1, + 0.2, 0.1 + }; + float *previous_gradient = calloc(config.output_length, sizeof(*previous_gradient)); + float *input_derivative = calloc(config.input_length, sizeof(*input_derivative)); + connected_layer *layer = make_connected_layer(config, + weights, + previous_gradient, + input_derivative, + NULL, + corrections); + + int is_first_layer = 1; + int result = free_connected_layer(layer, is_first_layer); + + assert_equal(result, 0); + + free(previous_gradient); + free(input_derivative); + + return 0; +} + +int test_connected_free_layer_no_corrections(void) { + layer_config config = stub_connected_config(); + float weights[] = { + 0.1, 0.2, 0.3, 0.4, + 0.6, 0.3 + }; + float *input = calloc(2, sizeof(*input)); + float *previous_gradient = calloc(config.output_length, sizeof(*previous_gradient)); + float *input_derivative = calloc(config.input_length, sizeof(*input_derivative)); + connected_layer *layer = make_connected_layer(config, + weights, + previous_gradient, + input_derivative, + input, + NULL); + + int is_first_layer = 0; + int result = free_connected_layer(layer, is_first_layer); + + assert_equal(result, 0); + + free(input); + free(previous_gradient); + free(input_derivative); + + return 0; +} + +layer_config stub_connected_config(void) { + layer_config config; + config.type = CONNECTED; + config.input_length = 2; + config.output_length = 2; + config.activation = RELU; + return config; +} diff --git a/tests/src/test_connected.h b/tests/src/test_connected.h new file mode 100644 index 0000000..d594db2 --- /dev/null +++ b/tests/src/test_connected.h @@ -0,0 +1,21 @@ +// +// test_connected.h +// test +// +// Created by Pavel Kondrashkov on 5/25/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef test_connected_h +#define test_connected_h + +#include + +int test_connected_make_connected_layer(void); +int test_connected_forward(void); +int test_connected_backward(void); +int test_connected_calc_corrections(void); +int test_connected_free_layer_first_layer(void); +int test_connected_free_layer_no_corrections(void); + +#endif /* test_connected_h */ diff --git a/tests/src/test_loss.c b/tests/src/test_loss.c new file mode 100644 index 0000000..be90f68 --- /dev/null +++ b/tests/src/test_loss.c @@ -0,0 +1,152 @@ +// +// test_loss.c +// test +// +// Created by Pavel Kondrashkov on 5/28/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "test_loss.h" +#include +#include "unit_test.h" +#include "config.h" +#include "loss.h" + +layer_config stub_loss_config(loss_type type); + +int test_loss_make_loss_layer(void) { + layer_config config = stub_loss_config(MSQE); + float input[] = { 0.25, 0.75 }; + float *previous_gradient = calloc(config.output_length, sizeof(*previous_gradient)); + float *input_derivative = calloc(config.input_length, sizeof(*input_derivative)); + float *error = calloc(1, sizeof(*error)); + + loss_layer *layer = make_loss_layer(config, previous_gradient, input, input_derivative, error, SCALAR_DERIVATIVE_MODE); + + assert_equal_float(layer->input[0], input[0]); + assert_equal_float(layer->input[1], input[1]); + assert_equal_ptr(layer->input, input); + assert_equal_ptr(layer->input_derivative, input_derivative); + + assert_equal(layer->type, LOSS); + assert_equal(layer->loss, config.loss); + assert_not_equal(layer->loss, NDEF_LOSS); + + assert_equal(layer->size, config.input_length); + assert_equal(layer->mode, SCALAR_DERIVATIVE_MODE); + + assert_equal(layer->target[0], 0); + assert_equal(layer->target[1], 0); + + assert_equal_ptr(layer->previous_gradients, previous_gradient); + assert_equal_ptr(layer->error, error); + + free(previous_gradient); + free(input_derivative); + free(error); + free_loss_layer(layer); + + return 0; +} + +int test_loss_forward(void) { + layer_config config = stub_loss_config(MSQE); + float input[] = { 0.25, 0.75 }; + float *previous_gradient = calloc(config.output_length, sizeof(*previous_gradient)); + float *input_derivative = calloc(config.input_length, sizeof(*input_derivative)); + float *error = calloc(1, sizeof(*error)); + + loss_layer *layer = make_loss_layer(config, previous_gradient, input, input_derivative, error, SCALAR_DERIVATIVE_MODE); + layer->target[0] = 0; + layer->target[1] = 1; + + loss_forward(layer); + + assert_equal_float(*layer->error, 0.0625); + + free(previous_gradient); + free(input_derivative); + free(error); + free_loss_layer(layer); + + return 0; +} + +int test_loss_backward_scalar_msqe(void) { + layer_config config = stub_loss_config(MSQE); + float input[] = { 0.909297427, -0.544921111 }; // sin(2), sin(10) + float *previous_gradient = calloc(config.output_length, sizeof(*previous_gradient)); + float input_derivative[] = { -0.416146837, -0.839071529 }; // cos(2), cos(10) + float *error = calloc(1, sizeof(*error)); + + loss_layer *layer = make_loss_layer(config, previous_gradient, input, input_derivative, error, SCALAR_DERIVATIVE_MODE); + layer->target[0] = 0.1; + layer->target[1] = 0.9; + + loss_backward(layer); + + assert_equal_float(layer->previous_gradients[0], -0.3367865644); + assert_equal_float(layer->previous_gradients[1], 1.2123921659); + + free(previous_gradient); + free(error); + free_loss_layer(layer); + + return 0; +} + +int test_loss_backward_softmax_msqe(void) { + layer_config config = stub_loss_config(MSQE); + float input[] = { 0.909297427, -0.544921111 }; // sin(2), sin(10) + float *previous_gradient = calloc(config.output_length, sizeof(*previous_gradient)); + float input_derivative[] = { -0.416146837, -0.839071529 }; // cos(2), cos(10) + float *error = calloc(1, sizeof(*error)); + + loss_layer *layer = make_loss_layer(config, previous_gradient, input, input_derivative, error, SOFTMAX_MODE); + layer->target[0] = 0.1; + layer->target[1] = 0.9; + + loss_backward(layer); + + assert_equal_float(layer->previous_gradients[0], 0.46775037); + assert_equal_float(layer->previous_gradients[1], 0.50046974); + + free(previous_gradient); + free(error); + free_loss_layer(layer); + + return 0; +} + +int test_loss_backward_softmax_cross_entropy(void) { + layer_config config = stub_loss_config(CROSS_ENTROPY); + float input[] = { 0.909297427, -0.544921111 }; // sin(2), sin(10) + float *previous_gradient = calloc(config.output_length, sizeof(*previous_gradient)); + float input_derivative[] = { -0.416146837, -0.839071529 }; // cos(2), cos(10) + float *error = calloc(1, sizeof(*error)); + + loss_layer *layer = make_loss_layer(config, previous_gradient, input, input_derivative, error, SOFTMAX_MODE); + layer->target[0] = 0.1; + layer->target[1] = 0.9; + + loss_backward(layer); + + assert_equal_float(layer->previous_gradients[0], 0.8092973); + assert_equal_float(layer->previous_gradients[1], -1.444921); + + free(previous_gradient); + free(error); + free_loss_layer(layer); + + return 0; +} + +layer_config stub_loss_config(loss_type type) { + layer_config config; + config.type = LOSS; + config.loss = type; + config.input_length = 2; + config.output_length = 2; + + return config; +} diff --git a/tests/src/test_loss.h b/tests/src/test_loss.h new file mode 100644 index 0000000..f6bb9b1 --- /dev/null +++ b/tests/src/test_loss.h @@ -0,0 +1,21 @@ +// +// test_loss.h +// test +// +// Created by Pavel Kondrashkov on 5/28/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef test_loss_h +#define test_loss_h + +#include + +int test_loss_make_loss_layer(void); +int test_loss_forward(void); +int test_loss_backward_scalar_msqe(void); +int test_loss_backward_softmax_msqe(void); +int test_loss_backward_scalar_cross_entropy(void); +int test_loss_backward_softmax_cross_entropy(void); + +#endif /* test_loss_h */ diff --git a/tests/src/test_math.c b/tests/src/test_math.c new file mode 100644 index 0000000..b0774e7 --- /dev/null +++ b/tests/src/test_math.c @@ -0,0 +1,344 @@ +// +// test_math.c +// test +// +// Created by Pavel Kondrashkov on 5/24/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "test_math.h" +#include "unit_test.h" +#include "math.h" +#include "config.h" + +int test_math_linear(void) { + float neuron1 = 1.2; + float neuron2 = -1.2; + float neuron3 = 0; + + neuron1 = linear(neuron1); + neuron2 = linear(neuron2); + neuron3 = linear(neuron3); + + assert_equal_float(neuron1, 1.2); + assert_equal_float(neuron2, -1.2); + assert_equal_float(neuron3, 0); + + return 0; +} + +int test_math_derivative_linear(void) { + float neuron1 = 1.2; + float neuron2 = -1.2; + float neuron3 = 0; + + neuron1 = derivative_linear(neuron1); + neuron2 = derivative_linear(neuron2); + neuron3 = derivative_linear(neuron3); + + assert_equal_float(neuron1, 1); + assert_equal_float(neuron2, 1); + assert_equal_float(neuron3, 1); + + return 0; +} + +int test_math_relu(void) { + float neuron1 = 1.2; + float neuron2 = -1.2; + float neuron3 = 0; + + neuron1 = relu(neuron1); + neuron2 = relu(neuron2); + neuron3 = relu(neuron3); + + assert_equal_float(neuron1, 1.2); + assert_equal_float(neuron2, 0); + assert_equal_float(neuron3, 0); + + return 0; +} + +int test_math_derivative_relu(void) { + float neuron1 = 1.2; + float neuron2 = -1.2; + float neuron3 = 0; + + neuron1 = derivative_relu(neuron1); + neuron2 = derivative_relu(neuron2); + neuron3 = derivative_relu(neuron3); + + assert_equal_float(neuron1, 1); + assert_equal_float(neuron2, 0); + assert_equal_float(neuron3, 0); + + return 0; +} + +int test_math_th(void) { + float neuron1 = 1.2; + float neuron2 = -1.2; + float neuron3 = -90; + float neuron4 = 90; + + neuron1 = th(neuron1); + neuron2 = th(neuron2); + neuron3 = th(neuron3); + neuron4 = th(neuron4); + + assert_equal_float(neuron1, 0.8336546); + assert_equal_float(neuron2, -0.8336546); + assert_equal_float(neuron3, -1); + assert_equal_float(neuron4, 1); + + return 0; +} + +int test_math_derivative_th(void) { + float neuron1 = 1.2; + float neuron2 = -1.2; + float neuron3 = 0; + float neuron4 = 20; + + neuron1 = derivative_th(neuron1); + neuron2 = derivative_th(neuron2); + neuron3 = derivative_th(neuron3); + neuron4 = derivative_th(neuron4); + + assert_equal_float(neuron1, 0.30502); + assert_equal_float(neuron2, 0.30502); + assert_equal_float(neuron3, 1); + assert_equal_float(neuron4, 0); + + return 0; +} + +int test_math_logistic(void) { + float neuron1 = 1.2; + float neuron2 = -1.2; + float neuron3 = -90; + float neuron4 = 90; + + neuron1 = logistic(neuron1); + neuron2 = logistic(neuron2); + neuron3 = logistic(neuron3); + neuron4 = logistic(neuron4); + + assert_equal_float(neuron1, 0.7685247); + assert_equal_float(neuron2, 0.2314752); + assert_equal_float(neuron3, 0); + assert_equal_float(neuron4, 1); + + return 0; +} + +int test_math_derivative_logistic(void) { + float neuron1 = 1.2; + float neuron2 = -1.2; + float neuron3 = 0; + + neuron1 = derivative_logistic(neuron1); + neuron2 = derivative_logistic(neuron2); + neuron3 = derivative_logistic(neuron3); + + assert_equal_float(neuron1, 0.1778944); + assert_equal_float(neuron2, 0.1778944); + assert_equal_float(neuron3, 0.25); + + return 0; +} + +int test_math_msqe(void) { + float vector1[] = { 0, 0, 0, 1 }; + float vector2[] = { 1, 1, 1, 1 }; + float vector3[] = { 1, 1, 1, 0 }; + + float target[] = { 0, 0, 0, 1 }; + int length = 4; + + float error1 = msqe(vector1, target, length); + float error2 = msqe(vector2, target, length); + float error3 = msqe(vector3, target, length); + + assert_equal_float(error1, 0); + assert_equal_float(error2, 1.5); + assert_equal_float(error3, 2); + + return 0; +} + +int test_math_cross_entropy(void) { + float vector1[] = { 0.1, 0.25, 0.25, 0.4 }; + float vector2[] = { 0.25, 0.25, 0.25, 0.25 }; + float vector3[] = { 0.1, 0.1, 0.1, 0.7 }; + + float target[] = { 0.5, 0, 0.4, 0.1 }; + int length = 4; + + float error1 = cross_entropy(vector1, target, length); + float error2 = cross_entropy(vector2, target, length); + float error3 = cross_entropy(vector3, target, length); + + assert_equal_float(error1, 1.7974393); + assert_equal_float(error2, 1.3862943); + assert_equal_float(error3, 2.1079940); + + return 0; +} + +int test_math_softmax(void) { + int length = 3; + float neurons[] = { 1.0, 2.0, 3.0 }; + + softmax(neurons, length); + + assert_equal_float(neurons[0], 0.0900305); + assert_equal_float(neurons[1], 0.2447284); + assert_equal_float(neurons[2], 0.6652409); + return 0; +} + +int test_math_sqroot(void) { + float value1 = 2.0; + float value2 = 100.0; + float value3 = 15.0; + float value4 = -15.0; + + value1 = sqroot(value1); + value2 = sqroot(value2); + value3 = sqroot(value3); + value4 = sqroot(value4); + + assert_equal_float(value1, 1.4142135); + assert_equal_float(value2, 10); + assert_equal_float(value3, 3.8729834); + assert_equal_float(value4, 0); + + return 0; +} + +int test_math_activate(void) { + int types_count = 4; + activation_type types[] = { RELU, TH, LOGISTIC, LINEAR }; + int i; + for (i = 0; i < types_count; i++) { + float neuron1 = 1.2; + float neuron2 = -1.2; + float neuron3 = 0; + + activate(&neuron1, types[i]); + activate(&neuron2, types[i]); + activate(&neuron3, types[i]); + + switch (types[i]) { + case RELU: { + assert_equal_float(neuron1, 1.2); + assert_equal_float(neuron2, 0); + assert_equal_float(neuron3, 0); + } + break; + case TH: { + assert_equal_float(neuron1, 0.8336546); + assert_equal_float(neuron2, -0.8336546); + assert_equal_float(neuron3, 0); + } + break; + case LOGISTIC: { + assert_equal_float(neuron1, 0.7685247); + assert_equal_float(neuron2, 0.2314752); + assert_equal_float(neuron3, 0.5); + } + break; + case LINEAR: { + assert_equal_float(neuron1, 1.2); + assert_equal_float(neuron2, -1.2); + assert_equal_float(neuron3, 0); + } + break; + default: + fail(); + break; + } + } + + return 0; +} + +int test_math_derivative(void) { + float neuron1 = 1.2; + float neuron2 = -1.2; + float neuron3 = 0; + int types_count = 4; + activation_type types[] = { RELU, TH, LOGISTIC, LINEAR }; + int i; + for (i = 0; i < types_count; i++) { + + float derivative1 = derivative(neuron1, types[i]); + float derivative2 = derivative(neuron2, types[i]); + float derivative3 = derivative(neuron3, types[i]); + + switch (types[i]) { + case RELU: { + assert_equal_float(derivative1, 1); + assert_equal_float(derivative2, 0); + assert_equal_float(derivative3, 0); + } + break; + case TH: { + assert_equal_float(derivative1, 0.30502); + assert_equal_float(derivative2, 0.30502); + assert_equal_float(derivative3, 1); + } + break; + case LOGISTIC: { + assert_equal_float(derivative1, 0.1778944); + assert_equal_float(derivative2, 0.1778944); + assert_equal_float(derivative3, 0.25); + } + break; + case LINEAR: { + assert_equal_float(derivative1, 1); + assert_equal_float(derivative2, 1); + assert_equal_float(derivative3, 1); + } + break; + default: + fail(); + break; + } + } + + return 0; +} + +int test_math_average(void) { + float values[] = { 1.0, 2.0, 3.0, 4.0 }; + int length = 4; + + float result = average(values, length); + + assert_equal_float(result, 2.5); + return 0; +} + +int test_math_min(void) { + float higher = 10.1; + float lower = 4.4; + + float result = min(higher, lower); + + assert_equal_float(result, lower); + + return 0; +} + +int test_math_max(void) { + float higher = 10.1; + float lower = 4.4; + + float result = max(higher, lower); + + assert_equal_float(result, higher); + + return 0; +} diff --git a/tests/src/test_math.h b/tests/src/test_math.h new file mode 100644 index 0000000..bfbc4f4 --- /dev/null +++ b/tests/src/test_math.h @@ -0,0 +1,32 @@ +// +// test_math.h +// test +// +// Created by Pavel Kondrashkov on 5/24/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef test_math_h +#define test_math_h + +#include + +int test_math_linear(void); +int test_math_derivative_linear(void); +int test_math_relu(void); +int test_math_derivative_relu(void); +int test_math_th(void); +int test_math_derivative_th(void); +int test_math_logistic(void); +int test_math_derivative_logistic(void); +int test_math_msqe(void); +int test_math_cross_entropy(void); +int test_math_softmax(void); +int test_math_sqroot(void); +int test_math_activate(void); +int test_math_derivative(void); +int test_math_average(void); +int test_math_min(void); +int test_math_max(void); + +#endif /* test_math_h */ diff --git a/tests/src/test_run.c b/tests/src/test_run.c new file mode 100644 index 0000000..f85bfe2 --- /dev/null +++ b/tests/src/test_run.c @@ -0,0 +1,166 @@ +// +// test_run.c +// test +// +// Created by Pavel Kondrashkov on 6/6/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "test_run.h" +#include +#include "unit_test.h" +#include "run.h" +#include "connected.h" + +layer_config *stub_layer_configs(void); + +int test_run_make_run_root(void) { + layer_config *configs = stub_layer_configs(); + int layers_count = 2; + int input_size = 4; + float *input_data = malloc(input_size * sizeof(*input_data)); + + run_root *root = make_run_root(configs, layers_count, input_data); + + assert_equal_ptr(root->input_data, input_data); + assert_not_equal(root->weights, NULL); + assert_not_equal(root->weights[root->network.count - 1].count, 0); + assert_not_equal(root->weights[root->network.count - 1].values, NULL); + + free_run_root(root); + free(configs); + + return 0; +} + +int test_run_free_run_root(void) { + layer_config *configs = stub_layer_configs(); + int layers_count = 2; + int input_size = 4; + float *input_data = malloc(input_size * sizeof(*input_data)); + run_root *root = make_run_root(configs, layers_count, input_data); + + int result = free_run_root(root); + + assert_equal(result, 0); + + free(configs); + + return 0; +} + +int test_run_make_run_weights(void) { + layer_config *configs = stub_layer_configs(); + + run_weights weights = make_run_weights(configs[0]); + int expected_values_count = (configs[0].input_length + 1) * configs[0].output_length; + + assert_equal(weights.count, expected_values_count); + assert_not_equal(weights.values, NULL); + + free_run_weights(weights); + free(configs); + + return 0; +} + +int test_run_free_run_weights(void) { + layer_config *configs = stub_layer_configs(); + run_weights weights = make_run_weights(configs[0]); + + int result = free_run_weights(weights); + + assert_equal(result, 0); + + free(configs); + + return 0; +} + +int test_run_make_network(void) { + layer_config *configs = stub_layer_configs(); + int layers_count = 2; + run_weights *weights = malloc((layers_count) * sizeof(*weights)); + int input_size = 4; + float *input_data = malloc(input_size * sizeof(*input_data)); + + run_network net = make_run_network(configs, layers_count, weights, input_data); + + float *prev_output = input_data; + for (int i = 0; i < layers_count; i++) { + switch (configs[i].type) { + case CONVOLUTIONAL: + break; + case CONNECTED: { + connected_layer *layer = net.layers[i]; + assert_equal_ptr(layer->input, prev_output); + prev_output = layer->output; + } + break; + default: + fail(); + } + } + + free_run_network(net); + free(configs); + + return 0; +} + +int test_run_free_network(void) { + layer_config *configs = stub_layer_configs(); + int layers_count = 2; + run_weights *weights = malloc((layers_count) * sizeof(*weights)); + int input_size = 4; + float *input_data = malloc(input_size * sizeof(*input_data)); + run_network net = make_run_network(configs, layers_count, weights, input_data); + + int result = free_run_network(net); + + assert_equal(result, 0); + + free(configs); + + return 0; +} + +int test_run_free_layer(void) { + layer_config *configs = stub_layer_configs(); + int layers_count = 2; + run_weights *weights = malloc((layers_count) * sizeof(*weights)); + int input_size = 4; + float *input_data = malloc(input_size * sizeof(*input_data)); + run_network net = make_run_network(configs, layers_count, weights, input_data); + + int result = 0; + int i; + for (i = 0; i < net.count; ++i) { + int is_first_layer = i == 0; + result += free_layer(net.layers[i], is_first_layer); + } + + assert_equal(result, 0); + + free(configs); + + return 0; +} + +layer_config *stub_layer_configs(void) { + int size = 2; + layer_config *configs = malloc(size * sizeof(layer_config)); + + configs[0] = make_layer_config(); + configs[0].type = CONNECTED; + configs[0].input_length = 4; + configs[0].output_length = 2; + configs[0].activation = RELU; + + configs[1] = make_layer_config(); + configs[1].type = CONNECTED; + configs[1].input_length = 2; + configs[1].output_length = 1; + configs[1].activation = SOFTMAX; + return configs; +} diff --git a/tests/src/test_run.h b/tests/src/test_run.h new file mode 100644 index 0000000..fb94ea7 --- /dev/null +++ b/tests/src/test_run.h @@ -0,0 +1,22 @@ +// +// test_run.h +// test +// +// Created by Pavel Kondrashkov on 6/6/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef test_run_h +#define test_run_h + +#include + +int test_run_make_run_root(void); +int test_run_free_run_root(void); +int test_run_make_run_weights(void); +int test_run_free_run_weights(void); +int test_run_make_network(void); +int test_run_free_network(void); +int test_run_free_layer(void); + +#endif /* test_run_h */ diff --git a/tests/src/test_training.c b/tests/src/test_training.c new file mode 100644 index 0000000..b76ed28 --- /dev/null +++ b/tests/src/test_training.c @@ -0,0 +1,117 @@ +// +// test_training.c +// test +// +// Created by Pavel Kondrashkov on 5/31/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "test_training.h" +#include +#include +#include "unit_test.h" +#include "train.h" +#include "config.h" + +int test_training_validation_data_size(void) { + int raw_input_data_size = 100; + int validation_percentage = 20; + + int result = validation_size(raw_input_data_size, validation_percentage); + + assert_equal(result, 5); + + return 0; +} + +int test_training_validation_data_size_min(void) { + int raw_input_data_size = 11; + int validation = 2; + + int result = validation_size(raw_input_data_size, validation); + + assert_equal(result, 5); + + return 0; +} + +int test_training_prepare_data_set(void) { + int raw_data_count = 11; + input_data *raw_input_data = make_input_data(raw_data_count); + for (int i = 0; i < raw_data_count; i++) { + raw_input_data->items[i] = malloc(3 * sizeof(*raw_input_data->items[i])); + sprintf(raw_input_data->items[i], "%02d", i); + } + int batch_size = validation_size(raw_data_count, 5); + data_set *dataset = prepare_data_set(raw_input_data, batch_size); + + + int rest = raw_data_count % batch_size; + int validation_data_count = batch_size + rest; + int train_data_count = raw_data_count - batch_size - rest; + int sets_count = raw_input_data->count / batch_size; + + assert_equal(dataset->count, sets_count); + assert_equal(dataset->training_set[0]->count, train_data_count); + assert_equal(dataset->validation_set[0]->count, validation_data_count); + + assert_equal(dataset->training_set[dataset->count - 1]->count, train_data_count); + assert_equal(dataset->validation_set[dataset->count - 1]->count, validation_data_count); + + assert_equal_string(dataset->validation_set[0]->items[0], "00"); + assert_equal_string(dataset->validation_set[0]->items[1], "01"); + assert_equal_string(dataset->validation_set[0]->items[2], "10"); + assert_equal_string(dataset->training_set[0]->items[0], "02"); + assert_equal_string(dataset->training_set[0]->items[1], "03"); + assert_equal_string(dataset->training_set[0]->items[2], "04"); + assert_equal_string(dataset->training_set[0]->items[3], "05"); + assert_equal_string(dataset->training_set[0]->items[4], "06"); + assert_equal_string(dataset->training_set[0]->items[5], "07"); + assert_equal_string(dataset->training_set[0]->items[6], "08"); + assert_equal_string(dataset->training_set[0]->items[7], "09"); + + assert_equal_string(dataset->validation_set[dataset->count - 1]->items[0], "08"); + assert_equal_string(dataset->validation_set[dataset->count - 1]->items[1], "09"); + assert_equal_string(dataset->validation_set[dataset->count - 1]->items[2], "10"); + assert_equal_string(dataset->training_set[dataset->count - 1]->items[0], "00"); + assert_equal_string(dataset->training_set[dataset->count - 1]->items[1], "01"); + assert_equal_string(dataset->training_set[dataset->count - 1]->items[2], "02"); + assert_equal_string(dataset->training_set[dataset->count - 1]->items[3], "03"); + assert_equal_string(dataset->training_set[dataset->count - 1]->items[4], "04"); + assert_equal_string(dataset->training_set[dataset->count - 1]->items[5], "05"); + assert_equal_string(dataset->training_set[dataset->count - 1]->items[6], "06"); + assert_equal_string(dataset->training_set[dataset->count - 1]->items[7], "07"); + + free_input_data(raw_input_data); + free_data_set(dataset); + return 0; +} + +int test_training_make_data_set(void) { + int sets_count = 3; + int train_data_count = 100; + int validation_data_count = 20; + data_set *dataset = make_data_set(sets_count, train_data_count, validation_data_count); + + assert_not_equal(dataset->training_set, NULL); + assert_not_equal(dataset->validation_set, NULL); + assert_equal(dataset->count, sets_count); + assert_equal(dataset->training_set[0]->count, train_data_count); + assert_equal(dataset->validation_set[0]->count, validation_data_count); + + free_data_set(dataset); + + return 0; +} + +int test_training_free_data_set(void) { + int sets_count = 3; + int train_data_count = 100; + int validation_data_count = 20; + data_set *dataset = make_data_set(sets_count, train_data_count, validation_data_count); + + int result = free_data_set(dataset); + + assert_equal(result, 0); + return 0; +} diff --git a/tests/src/test_training.h b/tests/src/test_training.h new file mode 100644 index 0000000..bd363c6 --- /dev/null +++ b/tests/src/test_training.h @@ -0,0 +1,19 @@ +// +// test_training.h +// test +// +// Created by Pavel Kondrashkov on 5/31/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef test_training_h +#define test_training_h + +#include + +int test_training_validation_data_size(void); +int test_training_validation_data_size_min(void); +int test_training_prepare_data_set(void); +int test_training_make_data_set(void); +int test_training_free_data_set(void); +#endif /* test_training_h */ diff --git a/tests/src/test_utils.c b/tests/src/test_utils.c new file mode 100644 index 0000000..1957dcb --- /dev/null +++ b/tests/src/test_utils.c @@ -0,0 +1,122 @@ +// +// test_utils.c +// utils_test +// +// Created by Pavel Kondrashkov on 5/23/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "test_utils.h" +#include "unit_test.h" +#include "utils.h" + +int test_utils_make_matrix(void) { + float vector[] = { + 0, 1, + 10, 11 + }; + int r = 2; + int c = 2; + float ***matrix = make_matrix(r, c, 1, 0, vector); + + assert_equal_float(*(matrix[0][0]), 0); + assert_equal_float(*(matrix[0][1]), 1); + assert_equal_float(*(matrix[1][0]), 10); + assert_equal_float(*(matrix[1][1]), 11); + + free_matrix(matrix, r); + return 0; +} + +int test_utils_make_tensor(void) { + float vector[] = { + 0, 10, 20, 1, 11, 21, 2, 12, 22, 3, 13, 23 + }; + + int r = 2; + int c = 2; + int d = 3; + float ****tensor = make_tensor(r, c, d, 0, vector); + + assert_equal_float(*(tensor[0][0][0]), 0); + assert_equal_float(*(tensor[0][0][1]), 1); + assert_equal_float(*(tensor[0][1][0]), 2); + assert_equal_float(*(tensor[0][1][1]), 3); + + assert_equal_float(*(tensor[1][0][0]), 10); + assert_equal_float(*(tensor[1][0][1]), 11); + assert_equal_float(*(tensor[1][1][0]), 12); + assert_equal_float(*(tensor[1][1][1]), 13); + + assert_equal_float(*(tensor[2][0][0]), 20); + assert_equal_float(*(tensor[2][0][1]), 21); + assert_equal_float(*(tensor[2][1][0]), 22); + assert_equal_float(*(tensor[2][1][1]), 23); + + free_tensor(tensor, r, d); + return 0; +} + +int test_utils_make_array_of_tensors(void) { + float vector[] = { + 0, 10, 20, 1, 11, 21, 2, 12, 22, 3, 13, 23, + 100, 110, 120, 101, 111, 121, 102, 112, 122, 103, 113, 123, + 200, 210, 220, 201, 211, 221, 202, 212, 222, 203, 213, 223 + }; + + int r = 2; + int c = 2; + int d = 3; + int l = 3; + float *****tensor = make_array_of_tensors(r, c, d, l, vector); + + assert_equal_float(*(tensor[0][0][0][0]), 0); + assert_equal_float(*(tensor[0][0][0][1]), 1); + assert_equal_float(*(tensor[0][0][1][0]), 2); + assert_equal_float(*(tensor[0][0][1][1]), 3); + + assert_equal_float(*(tensor[0][1][0][0]), 10); + assert_equal_float(*(tensor[0][1][0][1]), 11); + assert_equal_float(*(tensor[0][1][1][0]), 12); + assert_equal_float(*(tensor[0][1][1][1]), 13); + + assert_equal_float(*(tensor[0][2][0][0]), 20); + assert_equal_float(*(tensor[0][2][0][1]), 21); + assert_equal_float(*(tensor[0][2][1][0]), 22); + assert_equal_float(*(tensor[0][2][1][1]), 23); + + + assert_equal_float(*(tensor[1][0][0][0]), 100); + assert_equal_float(*(tensor[1][0][0][1]), 101); + assert_equal_float(*(tensor[1][0][1][0]), 102); + assert_equal_float(*(tensor[1][0][1][1]), 103); + + assert_equal_float(*(tensor[1][1][0][0]), 110); + assert_equal_float(*(tensor[1][1][0][1]), 111); + assert_equal_float(*(tensor[1][1][1][0]), 112); + assert_equal_float(*(tensor[1][1][1][1]), 113); + + assert_equal_float(*(tensor[1][2][0][0]), 120); + assert_equal_float(*(tensor[1][2][0][1]), 121); + assert_equal_float(*(tensor[1][2][1][0]), 122); + assert_equal_float(*(tensor[1][2][1][1]), 123); + + + assert_equal_float(*(tensor[2][0][0][0]), 200); + assert_equal_float(*(tensor[2][0][0][1]), 201); + assert_equal_float(*(tensor[2][0][1][0]), 202); + assert_equal_float(*(tensor[2][0][1][1]), 203); + + assert_equal_float(*(tensor[2][1][0][0]), 210); + assert_equal_float(*(tensor[2][1][0][1]), 211); + assert_equal_float(*(tensor[2][1][1][0]), 212); + assert_equal_float(*(tensor[2][1][1][1]), 213); + + assert_equal_float(*(tensor[2][2][0][0]), 220); + assert_equal_float(*(tensor[2][2][0][1]), 221); + assert_equal_float(*(tensor[2][2][1][0]), 222); + assert_equal_float(*(tensor[2][2][1][1]), 223); + + free_array_of_tensors(tensor, r, d, l); + return 0; +} diff --git a/tests/src/test_utils.h b/tests/src/test_utils.h new file mode 100644 index 0000000..1133da0 --- /dev/null +++ b/tests/src/test_utils.h @@ -0,0 +1,18 @@ +// +// test_utils.h +// utils_test +// +// Created by Pavel Kondrashkov on 5/23/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef test_utils_h +#define test_utils_h + +#include + +int test_utils_make_matrix(void); +int test_utils_make_tensor(void); +int test_utils_make_array_of_tensors(void); + +#endif /* test_utils_h */ diff --git a/tests/src/test_validation.c b/tests/src/test_validation.c new file mode 100644 index 0000000..a8c0c88 --- /dev/null +++ b/tests/src/test_validation.c @@ -0,0 +1,157 @@ +// +// test_validation.c +// test +// +// Created by Pavel Kondrashkov on 6/25/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include "test_validation.h" +#include "unit_test.h" +#include +#include "connected.h" +#include "validation.h" +#include "math.h" + +npx_config *stub_validation_npx_config(void); +data_set *stub_data_set(void); +train_weights *stub_train_weights(npx_config *npx); + +int test_validation_make_validation_root(void) { + npx_config *npx = stub_validation_npx_config(); + data_set *dataset = stub_data_set(); + train_weights *weights = stub_train_weights(npx); + validation_root *root = make_validation_root(npx, dataset, weights); + + int validations_count = dataset->validation_set[0]->count; + int networks_count = (int)min(validations_count, npx->settings->threads); + + assert_not_equal(root->networks, NULL); + assert_equal(root->networks_count, networks_count); + assert_equal_ptr(root->npx, npx); + assert_not_equal(root->batch_errors, NULL); + assert_equal_ptr(root->dataset, dataset); + + return 0; +} + +int test_validation_free_validation_root(void) { + npx_config *npx = stub_validation_npx_config(); + data_set *dataset = stub_data_set(); + train_weights *weights = stub_train_weights(npx); + validation_root *root = make_validation_root(npx, dataset, weights); + + int result = free_validation_root(root); + + assert_equal(result, 0); + return 0; +} + +int test_validation_make_validation_network(void) { + npx_config *npx = stub_validation_npx_config(); + train_weights *weights = stub_train_weights(npx); + float *batch_error_ref = malloc(npx->settings->batch * sizeof(batch_error_ref)); + + validation_network net = make_validation_network(npx, weights, &batch_error_ref); + + assert_equal_ptr(net.batch_errors_ref, &batch_error_ref);; + assert_not_equal(net.layers, NULL); + assert_equal(net.input_length, npx->settings->input_length); + assert_equal(net.target_length, npx->settings->target_length); + assert_equal(net.count, npx->size); + assert_equal(net.validation_items_count, 0); + assert_equal(net.validation_offset, 0); + assert_not_equal(net.error, NULL); + + return 0; +} + +int test_validation_free_validation_network(void) { + npx_config *npx = stub_validation_npx_config(); + train_weights *weights = stub_train_weights(npx); + + validation_network net = make_validation_network(npx, weights, NULL); + int result = free_validation_network(net); + + assert_equal(result, 0); + return 0; +} + +int test_validation_set_validation_offset(void) { + npx_config *npx = stub_validation_npx_config(); + train_weights *weights = stub_train_weights(npx); + float *batch_error_ref = malloc(npx->settings->batch * sizeof(batch_error_ref)); + int networks_count = 4; + validation_network networks[networks_count]; + for (int i = 0; i < networks_count; ++i) { + networks[i] = make_validation_network(npx, weights, &batch_error_ref); + } + int validation_data_count = 6321; + + set_validation_offset(validation_data_count, networks, networks_count); + + + + assert_equal(networks[0].validation_items_count, 1580); + assert_equal(networks[0].validation_offset, 0); + assert_equal(networks[1].validation_items_count, 1580); + assert_equal(networks[1].validation_offset, 1580); + assert_equal(networks[2].validation_items_count, 1580); + assert_equal(networks[2].validation_offset, 1580 + 1580); + assert_equal(networks[3].validation_items_count, 1580 + 1); + assert_equal(networks[3].validation_offset, 1580 + 1580 + 1580); + int sum = 0; + for (int i = 0; i < networks_count; ++i) { + sum += networks[i].validation_items_count; + } + assert_equal(sum, validation_data_count); + return 0; +} + +npx_config *stub_validation_npx_config(void) { + npx_config *npx = make_npx_config(); + npx->size = 2; + layer_config *net_configs = malloc(npx->size * sizeof(layer_config)); + net_configs[0] = make_layer_config(); + net_configs[0].type = CONNECTED; + net_configs[0].input_length = 4; + net_configs[0].output_length = 2; + net_configs[0].activation = RELU; + + net_configs[1] = make_layer_config(); + net_configs[1].type = CONNECTED; + net_configs[1].input_length = 2; + net_configs[1].output_length = 1; + net_configs[1].activation = SOFTMAX; + npx->net = net_configs; + + settings_config *settings = make_settings_config(); + settings->threads = 1; + settings->input_length = net_configs[0].input_length; + settings->target_length = net_configs[1].output_length; + npx->settings = settings; + + return npx; +} + +data_set *stub_data_set(void) { + int raw_data_count = 11; + input_data *raw_input_data = make_input_data(raw_data_count); + for (int i = 0; i < raw_data_count; i++) { + raw_input_data->items[i] = malloc(3 * sizeof(*raw_input_data->items[i])); + sprintf(raw_input_data->items[i], "%02d", i); + } + int batch_size = validation_size(raw_data_count, 2); + data_set *dataset = prepare_data_set(raw_input_data, batch_size); + return dataset; +} + +train_weights *stub_train_weights(npx_config *npx) { + train_params *params = malloc(sizeof(*params)); + params->batch = 1; + train_weights *weights = malloc((npx->size - 1) * sizeof(*weights)); + for (int i = 0; i < npx->size - 1; i++) { + weights[i] = make_train_weights(npx->net[i], params); + } + return weights; +} diff --git a/tests/src/test_validation.h b/tests/src/test_validation.h new file mode 100644 index 0000000..bf6b498 --- /dev/null +++ b/tests/src/test_validation.h @@ -0,0 +1,20 @@ +// +// test_validation.h +// test +// +// Created by Pavel Kondrashkov on 6/25/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef test_validation_h +#define test_validation_h + +#include + +int test_validation_make_validation_root(void); +int test_validation_free_validation_root(void); +int test_validation_make_validation_network(void); +int test_validation_free_validation_network(void); +int test_validation_set_validation_offset(void); + +#endif /* test_validation_h */ diff --git a/tests/src/unit_test.c b/tests/src/unit_test.c new file mode 100644 index 0000000..b321eac --- /dev/null +++ b/tests/src/unit_test.c @@ -0,0 +1,23 @@ +// +// unit_test.c +// netapix +// +// Created by Pavel Kondrashkov on 5/24/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#include +#include "unit_test.h" + +int test_passed = 0; +int test_failed = 0; + +void test(int (*func)(void), const char *name) { + int r = func(); + if (r == 0) { + test_passed++; + } else { + test_failed++; + printf("FAILED: %s (at line %d)\n", name, r); + } +} diff --git a/tests/src/unit_test.h b/tests/src/unit_test.h new file mode 100644 index 0000000..48af029 --- /dev/null +++ b/tests/src/unit_test.h @@ -0,0 +1,35 @@ +// +// unit_test.h +// netapix +// +// Created by Pavel Kondrashkov on 5/24/18. +// Copyright © 2018 Touchlane LLC. All rights reserved. +// + +#ifndef unit_test_h +#define unit_test_h + +#include +#include + +extern int test_passed; +extern int test_failed; + +/* Terminate current test with error */ +#define fail() return __LINE__ + +/* Successful end of the test case */ +#define done() return 0 + +/* Assert defines */ +#define assert(cond) do { if (!(cond)) fail(); } while (0) +#define assert_not_equal(a, b) assert( a != b ) +#define assert_equal(a, b) assert( a == b ) +#define assert_equal_float(a, b) assert( (fabs(a - b) < 1e-7) ) +#define assert_equal_string(a, b) assert( !strcmp((const char*)a, (const char*)b) ) +#define assert_equal_ptr(a, b) assert( (void*)(a) == (void*)(b)) + +/* Test runner */ +void test(int (*func)(void), const char *name); + +#endif /* unit_test_h */