diff --git a/Genann.h b/Genann.h new file mode 100644 index 0000000..fa37cdf --- /dev/null +++ b/Genann.h @@ -0,0 +1,361 @@ +// +// GENANN - Minimal C Artificial Neural Network +// +// Copyright (c) 2015, 2016 Lewis Van Winkle +// +// http://CodePlea.com +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgement in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// +// +// This software has been altered from its original state. Namely white space edits +// and formatting but most importantly the library has been moved into a single +// static inline header file. +// +// - Gustav Louw 2018 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +typedef double (*genann_actfun)(double a); + +typedef struct +{ + // How many inputs, outputs, and hidden neurons. + int inputs; + int hidden_layers; + int hidden; + int outputs; + // Which activation function to use for hidden neurons. Default: gennann_act_sigmoid_cached. + genann_actfun activation_hidden; + // Which activation function to use for output. Default: gennann_act_sigmoid_cached. + genann_actfun activation_output; + // Total number of weights, and size of weights buffer. + int total_weights; + // Total number of neurons + inputs and size of output buffer. + int total_neurons; + // All weights (total_weights long). + double *weight; + // Stores input array and output of each neuron (total_neurons long). + double *output; + // Stores delta of each hidden and output neuron (total_neurons - inputs long). + double *delta; + +} +Genann; + +static inline double genann_act_sigmoid(double a) +{ + return a < -45.0 ? 0 : a > 45.0 ? 1.0 : 1.0 / (1 + exp(-a)); +} + +static inline double genann_act_sigmoid_cached(double a) +{ + // If you're optimizing for memory usage, just + // delete this entire function and replace references + // of genann_act_sigmoid_cached to genann_act_sigmoid. + const double min = -15.0; + const double max = 15.0; + static double interval; + static int initialized = 0; + static double lookup[4096]; + const int lookup_size = sizeof(lookup) / sizeof(*lookup); + // Calculate entire lookup table on first run. + if(!initialized) + { + interval = (max - min) / lookup_size; + for(int i = 0; i < lookup_size; ++i) + lookup[i] = genann_act_sigmoid(min + interval * i); + // This is down here to make this thread safe. + initialized = 1; + } + const int i = (int) ((a - min) / interval + 0.5); + return i <= 0 ? lookup[0] : i >= lookup_size ? lookup[lookup_size - 1] : lookup[i]; +} + +static inline double genann_act_threshold(double a) +{ + return a > 0; +} + +static inline double genann_act_linear(double a) +{ + return a; +} + +// We use the following for uniform random numbers between 0 and 1. +// If you have a better function, redefine this macro. +static inline double genann_random() +{ + return (double) rand() / RAND_MAX; +} + +static inline void genann_randomize(Genann *ann) +{ + for(int i = 0; i < ann->total_weights; ++i) + { + double r = genann_random(); + // Sets weights from -0.5 to 0.5. + ann->weight[i] = r - 0.5; + } +} + +static inline Genann *genann_init(int inputs, int hidden_layers, int hidden, int outputs) +{ + if(hidden_layers < 0) + return 0; + if(inputs < 1) + return 0; + if(outputs < 1) + return 0; + if(hidden_layers > 0 && hidden < 1) + return 0; + const int hidden_weights = hidden_layers ? (inputs + 1) * hidden + (hidden_layers - 1) * (hidden + 1) * hidden : 0; + const int output_weights = (hidden_layers ? (hidden + 1) : (inputs + 1)) * outputs; + const int total_weights = hidden_weights + output_weights; + const int total_neurons = inputs + hidden * hidden_layers + outputs; + // Allocate extra size for weights, outputs, and deltas. + const int size = sizeof(Genann) + sizeof(double) * (total_weights + total_neurons + (total_neurons - inputs)); + Genann *ret = malloc(size); + if(!ret) + return 0; + ret->inputs = inputs; + ret->hidden_layers = hidden_layers; + ret->hidden = hidden; + ret->outputs = outputs; + ret->total_weights = total_weights; + ret->total_neurons = total_neurons; + // Set pointers. + ret->weight = (double*)((char*)ret + sizeof(Genann)); + ret->output = ret->weight + ret->total_weights; + ret->delta = ret->output + ret->total_neurons; + genann_randomize(ret); + ret->activation_hidden = genann_act_sigmoid_cached; + ret->activation_output = genann_act_sigmoid_cached; + return ret; +} + +static inline void genann_free(Genann *ann) +{ + // The weight, output, and delta pointers go to the same buffer. + free(ann); +} + +static inline Genann *genann_read(FILE *in) +{ + int inputs, hidden_layers, hidden, outputs; + errno = 0; + int rc = fscanf(in, "%d %d %d %d", &inputs, &hidden_layers, &hidden, &outputs); + if(rc < 4 || errno != 0) + { + perror("fscanf"); + return NULL; + } + Genann *ann = genann_init(inputs, hidden_layers, hidden, outputs); + for(int i = 0; i < ann->total_weights; ++i) + { + errno = 0; + rc = fscanf(in, " %le", ann->weight + i); + if(rc < 1 || errno != 0) + { + perror("fscanf"); + genann_free(ann); + return NULL; + } + } + return ann; +} + +static inline Genann *genann_copy(Genann const *ann) +{ + const int size = sizeof(Genann) + sizeof(double) * (ann->total_weights + ann->total_neurons + (ann->total_neurons - ann->inputs)); + Genann *ret = malloc(size); + if(!ret) + return 0; + memcpy(ret, ann, size); + // Set pointers. + ret->weight = (double*)((char*)ret + sizeof(Genann)); + ret->output = ret->weight + ret->total_weights; + ret->delta = ret->output + ret->total_neurons; + return ret; +} + +static inline double const *genann_run(Genann const *ann, double const *inputs) +{ + double const *w = ann->weight; + double *o = ann->output + ann->inputs; + double const *i = ann->output; + // Copy the inputs to the scratch area, where we also store each neuron's + // output, for consistency. This way the first layer isn't a special case. + memcpy(ann->output, inputs, sizeof(double) * ann->inputs); + const genann_actfun act = ann->activation_hidden; + const genann_actfun acto = ann->activation_output; + // Figure hidden layers, if any. + for(int h = 0; h < ann->hidden_layers; ++h) + { + for(int j = 0; j < ann->hidden; ++j) + { + double sum = *w++ * -1.0; + for(int k = 0; k < (h == 0 ? ann->inputs : ann->hidden); ++k) + { + sum += *w++ * i[k]; + } + *o++ = act(sum); + } + i += (h == 0 ? ann->inputs : ann->hidden); + } + double const *ret = o; + // Figure output layer. + for(int j = 0; j < ann->outputs; ++j) + { + double sum = *w++ * -1.0; + for(int k = 0; k < (ann->hidden_layers ? ann->hidden : ann->inputs); ++k) + sum += *w++ * i[k]; + *o++ = acto(sum); + } + // Sanity check that we used all weights and wrote all outputs. + assert(w - ann->weight == ann->total_weights); + assert(o - ann->output == ann->total_neurons); + return ret; +} + +static inline void genann_train(Genann const *ann, double const *inputs, double const *desired_outputs, double learning_rate) { + // To begin with, we must run the network forward. + genann_run(ann, inputs); + // First set the output layer deltas. + { + // First output. + double const *o = ann->output + ann->inputs + ann->hidden * ann->hidden_layers; + // First delta. + double *d = ann->delta + ann->hidden * ann->hidden_layers; + // First desired output. + double const *t = desired_outputs; + // Set output layer deltas. + if(ann->activation_output == genann_act_linear) + { + for(int j = 0; j < ann->outputs; ++j) + { + *d++ = *t++ - *o++; + } + } + else + { + for(int j = 0; j < ann->outputs; ++j) + { + *d++ = (*t - *o) * *o * (1.0 - *o); + ++o; ++t; + } + } + } + // Set hidden layer deltas, start on last layer and work backwards. + // Note that loop is skipped in the case of hidden_layers == 0. + for(int h = ann->hidden_layers - 1; h >= 0; --h) + { + // Find first output and delta in this layer. + double const *o = ann->output + ann->inputs + (h * ann->hidden); + double *d = ann->delta + (h * ann->hidden); + // Find first delta in following layer (which may be hidden or output). + double const * const dd = ann->delta + ((h+1) * ann->hidden); + // Find first weight in following layer (which may be hidden or output). + double const * const ww = ann->weight + ((ann->inputs+1) * ann->hidden) + ((ann->hidden+1) * ann->hidden * (h)); + for(int j = 0; j < ann->hidden; ++j) + { + double delta = 0; + for(int k = 0; k < (h == ann->hidden_layers-1 ? ann->outputs : ann->hidden); ++k) + { + const double forward_delta = dd[k]; + const int windex = k * (ann->hidden + 1) + (j + 1); + const double forward_weight = ww[windex]; + delta += forward_delta * forward_weight; + } + *d = *o * (1.0-*o) * delta; + ++d; ++o; + } + } + // Train the outputs. + { + // Find first output delta. + // First output delta. + double const *d = ann->delta + ann->hidden * ann->hidden_layers; + // Find first weight to first output delta. + double *w = ann->weight + (ann->hidden_layers + ? ((ann->inputs+1) * ann->hidden + (ann->hidden+1) * ann->hidden * (ann->hidden_layers-1)) + : (0)); + // Find first output in previous layer. + double const * const i = ann->output + (ann->hidden_layers + ? (ann->inputs + (ann->hidden) * (ann->hidden_layers-1)) + : 0); + // Set output layer weights. + for(int j = 0; j < ann->outputs; ++j) { + for(int k = 0; k < (ann->hidden_layers ? ann->hidden : ann->inputs) + 1; ++k) { + if(k == 0) + { + *w++ += *d * learning_rate * -1.0; + } + else + { + *w++ += *d * learning_rate * i[k-1]; + } + } + ++d; + } + assert(w - ann->weight == ann->total_weights); + } + // Train the hidden layers. + for(int h = ann->hidden_layers - 1; h >= 0; --h) + { + // Find first delta in this layer. + double const *d = ann->delta + (h * ann->hidden); + // Find first input to this layer. + double const *i = ann->output + (h + ? (ann->inputs + ann->hidden * (h-1)) + : 0); + // Find first weight to this layer. + double *w = ann->weight + (h + ? ((ann->inputs+1) * ann->hidden + (ann->hidden+1) * (ann->hidden) * (h-1)) + : 0); + for(int j = 0; j < ann->hidden; ++j) + { + for(int k = 0; k < (h == 0 ? ann->inputs : ann->hidden) + 1; ++k) + { + if (k == 0) + { + *w++ += *d * learning_rate * -1.0; + } + else + { + *w++ += *d * learning_rate * i[k-1]; + } + } + ++d; + } + } +} + +static inline void genann_write(Genann const *ann, FILE *out) +{ + fprintf(out, "%d %d %d %d", ann->inputs, ann->hidden_layers, ann->hidden, ann->outputs); + for(int i = 0; i < ann->total_weights; ++i) + fprintf(out, " %.20e", ann->weight[i]); +} diff --git a/Makefile b/Makefile index 9218d60..3401fd5 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,6 @@ NAME = shaper SRCS = SRCS+= main.c -SRCS+= genann.c # CompSpec defined in windows environment. ifdef ComSpec diff --git a/genann.c b/genann.c deleted file mode 100644 index 0ec9b72..0000000 --- a/genann.c +++ /dev/null @@ -1,364 +0,0 @@ -/* - * GENANN - Minimal C Artificial Neural Network - * - * Copyright (c) 2015, 2016 Lewis Van Winkle - * - * http://CodePlea.com - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgement in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - * - */ - -#include "genann.h" - -#include -#include -#include -#include -#include -#include - -#define LOOKUP_SIZE 4096 - -double genann_act_sigmoid(double a) { - if (a < -45.0) return 0; - if (a > 45.0) return 1; - return 1.0 / (1 + exp(-a)); -} - - -double genann_act_sigmoid_cached(double a) { - /* If you're optimizing for memory usage, just - * delete this entire function and replace references - * of genann_act_sigmoid_cached to genann_act_sigmoid - */ - const double min = -15.0; - const double max = 15.0; - static double interval; - static int initialized = 0; - static double lookup[LOOKUP_SIZE]; - - /* Calculate entire lookup table on first run. */ - if (!initialized) { - interval = (max - min) / LOOKUP_SIZE; - int i; - for (i = 0; i < LOOKUP_SIZE; ++i) { - lookup[i] = genann_act_sigmoid(min + interval * i); - } - /* This is down here to make this thread safe. */ - initialized = 1; - } - - int i; - i = (int)((a-min)/interval+0.5); - if (i <= 0) return lookup[0]; - if (i >= LOOKUP_SIZE) return lookup[LOOKUP_SIZE-1]; - return lookup[i]; -} - - -double genann_act_threshold(double a) { - return a > 0; -} - - -double genann_act_linear(double a) { - return a; -} - - -genann *genann_init(int inputs, int hidden_layers, int hidden, int outputs) { - if (hidden_layers < 0) return 0; - if (inputs < 1) return 0; - if (outputs < 1) return 0; - if (hidden_layers > 0 && hidden < 1) return 0; - - - const int hidden_weights = hidden_layers ? (inputs+1) * hidden + (hidden_layers-1) * (hidden+1) * hidden : 0; - const int output_weights = (hidden_layers ? (hidden+1) : (inputs+1)) * outputs; - const int total_weights = (hidden_weights + output_weights); - - const int total_neurons = (inputs + hidden * hidden_layers + outputs); - - /* Allocate extra size for weights, outputs, and deltas. */ - const int size = sizeof(genann) + sizeof(double) * (total_weights + total_neurons + (total_neurons - inputs)); - genann *ret = malloc(size); - if (!ret) return 0; - - ret->inputs = inputs; - ret->hidden_layers = hidden_layers; - ret->hidden = hidden; - ret->outputs = outputs; - - ret->total_weights = total_weights; - ret->total_neurons = total_neurons; - - /* Set pointers. */ - ret->weight = (double*)((char*)ret + sizeof(genann)); - ret->output = ret->weight + ret->total_weights; - ret->delta = ret->output + ret->total_neurons; - - genann_randomize(ret); - - ret->activation_hidden = genann_act_sigmoid_cached; - ret->activation_output = genann_act_sigmoid_cached; - - return ret; -} - - -genann *genann_read(FILE *in) { - int inputs, hidden_layers, hidden, outputs; - int rc; - - errno = 0; - rc = fscanf(in, "%d %d %d %d", &inputs, &hidden_layers, &hidden, &outputs); - if (rc < 4 || errno != 0) { - perror("fscanf"); - return NULL; - } - - genann *ann = genann_init(inputs, hidden_layers, hidden, outputs); - - int i; - for (i = 0; i < ann->total_weights; ++i) { - errno = 0; - rc = fscanf(in, " %le", ann->weight + i); - if (rc < 1 || errno != 0) { - perror("fscanf"); - genann_free(ann); - - return NULL; - } - } - - return ann; -} - - -genann *genann_copy(genann const *ann) { - const int size = sizeof(genann) + sizeof(double) * (ann->total_weights + ann->total_neurons + (ann->total_neurons - ann->inputs)); - genann *ret = malloc(size); - if (!ret) return 0; - - memcpy(ret, ann, size); - - /* Set pointers. */ - ret->weight = (double*)((char*)ret + sizeof(genann)); - ret->output = ret->weight + ret->total_weights; - ret->delta = ret->output + ret->total_neurons; - - return ret; -} - - -void genann_randomize(genann *ann) { - int i; - for (i = 0; i < ann->total_weights; ++i) { - double r = GENANN_RANDOM(); - /* Sets weights from -0.5 to 0.5. */ - ann->weight[i] = r - 0.5; - } -} - - -void genann_free(genann *ann) { - /* The weight, output, and delta pointers go to the same buffer. */ - free(ann); -} - - -double const *genann_run(genann const *ann, double const *inputs) { - double const *w = ann->weight; - double *o = ann->output + ann->inputs; - double const *i = ann->output; - - /* Copy the inputs to the scratch area, where we also store each neuron's - * output, for consistency. This way the first layer isn't a special case. */ - memcpy(ann->output, inputs, sizeof(double) * ann->inputs); - - int h, j, k; - - const genann_actfun act = ann->activation_hidden; - const genann_actfun acto = ann->activation_output; - - /* Figure hidden layers, if any. */ - for (h = 0; h < ann->hidden_layers; ++h) { - for (j = 0; j < ann->hidden; ++j) { - double sum = *w++ * -1.0; - for (k = 0; k < (h == 0 ? ann->inputs : ann->hidden); ++k) { - sum += *w++ * i[k]; - } - *o++ = act(sum); - } - - - i += (h == 0 ? ann->inputs : ann->hidden); - } - - double const *ret = o; - - /* Figure output layer. */ - for (j = 0; j < ann->outputs; ++j) { - double sum = *w++ * -1.0; - for (k = 0; k < (ann->hidden_layers ? ann->hidden : ann->inputs); ++k) { - sum += *w++ * i[k]; - } - *o++ = acto(sum); - } - - /* Sanity check that we used all weights and wrote all outputs. */ - assert(w - ann->weight == ann->total_weights); - assert(o - ann->output == ann->total_neurons); - - return ret; -} - - -void genann_train(genann const *ann, double const *inputs, double const *desired_outputs, double learning_rate) { - /* To begin with, we must run the network forward. */ - genann_run(ann, inputs); - - int h, j, k; - - /* First set the output layer deltas. */ - { - double const *o = ann->output + ann->inputs + ann->hidden * ann->hidden_layers; /* First output. */ - double *d = ann->delta + ann->hidden * ann->hidden_layers; /* First delta. */ - double const *t = desired_outputs; /* First desired output. */ - - - /* Set output layer deltas. */ - if (ann->activation_output == genann_act_linear) { - for (j = 0; j < ann->outputs; ++j) { - *d++ = *t++ - *o++; - } - } else { - for (j = 0; j < ann->outputs; ++j) { - *d++ = (*t - *o) * *o * (1.0 - *o); - ++o; ++t; - } - } - } - - - /* Set hidden layer deltas, start on last layer and work backwards. */ - /* Note that loop is skipped in the case of hidden_layers == 0. */ - for (h = ann->hidden_layers - 1; h >= 0; --h) { - - /* Find first output and delta in this layer. */ - double const *o = ann->output + ann->inputs + (h * ann->hidden); - double *d = ann->delta + (h * ann->hidden); - - /* Find first delta in following layer (which may be hidden or output). */ - double const * const dd = ann->delta + ((h+1) * ann->hidden); - - /* Find first weight in following layer (which may be hidden or output). */ - double const * const ww = ann->weight + ((ann->inputs+1) * ann->hidden) + ((ann->hidden+1) * ann->hidden * (h)); - - for (j = 0; j < ann->hidden; ++j) { - - double delta = 0; - - for (k = 0; k < (h == ann->hidden_layers-1 ? ann->outputs : ann->hidden); ++k) { - const double forward_delta = dd[k]; - const int windex = k * (ann->hidden + 1) + (j + 1); - const double forward_weight = ww[windex]; - delta += forward_delta * forward_weight; - } - - *d = *o * (1.0-*o) * delta; - ++d; ++o; - } - } - - - /* Train the outputs. */ - { - /* Find first output delta. */ - double const *d = ann->delta + ann->hidden * ann->hidden_layers; /* First output delta. */ - - /* Find first weight to first output delta. */ - double *w = ann->weight + (ann->hidden_layers - ? ((ann->inputs+1) * ann->hidden + (ann->hidden+1) * ann->hidden * (ann->hidden_layers-1)) - : (0)); - - /* Find first output in previous layer. */ - double const * const i = ann->output + (ann->hidden_layers - ? (ann->inputs + (ann->hidden) * (ann->hidden_layers-1)) - : 0); - - /* Set output layer weights. */ - for (j = 0; j < ann->outputs; ++j) { - for (k = 0; k < (ann->hidden_layers ? ann->hidden : ann->inputs) + 1; ++k) { - if (k == 0) { - *w++ += *d * learning_rate * -1.0; - } else { - *w++ += *d * learning_rate * i[k-1]; - } - } - - ++d; - } - - assert(w - ann->weight == ann->total_weights); - } - - - /* Train the hidden layers. */ - for (h = ann->hidden_layers - 1; h >= 0; --h) { - - /* Find first delta in this layer. */ - double const *d = ann->delta + (h * ann->hidden); - - /* Find first input to this layer. */ - double const *i = ann->output + (h - ? (ann->inputs + ann->hidden * (h-1)) - : 0); - - /* Find first weight to this layer. */ - double *w = ann->weight + (h - ? ((ann->inputs+1) * ann->hidden + (ann->hidden+1) * (ann->hidden) * (h-1)) - : 0); - - - for (j = 0; j < ann->hidden; ++j) { - for (k = 0; k < (h == 0 ? ann->inputs : ann->hidden) + 1; ++k) { - if (k == 0) { - *w++ += *d * learning_rate * -1.0; - } else { - *w++ += *d * learning_rate * i[k-1]; - } - } - ++d; - } - - } - -} - - -void genann_write(genann const *ann, FILE *out) { - fprintf(out, "%d %d %d %d", ann->inputs, ann->hidden_layers, ann->hidden, ann->outputs); - - int i; - for (i = 0; i < ann->total_weights; ++i) { - fprintf(out, " %.20e", ann->weight[i]); - } -} - - diff --git a/genann.h b/genann.h deleted file mode 100644 index 3678ab6..0000000 --- a/genann.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * GENANN - Minimal C Artificial Neural Network - * - * Copyright (c) 2015, 2016 Lewis Van Winkle - * - * http://CodePlea.com - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgement in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - * - */ - - -#ifndef __GENANN_H__ -#define __GENANN_H__ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef GENANN_RANDOM -/* We use the following for uniform random numbers between 0 and 1. - * If you have a better function, redefine this macro. */ -#define GENANN_RANDOM() (((double)rand())/RAND_MAX) -#endif - - -typedef double (*genann_actfun)(double a); - - -typedef struct genann { - /* How many inputs, outputs, and hidden neurons. */ - int inputs, hidden_layers, hidden, outputs; - - /* Which activation function to use for hidden neurons. Default: gennann_act_sigmoid_cached*/ - genann_actfun activation_hidden; - - /* Which activation function to use for output. Default: gennann_act_sigmoid_cached*/ - genann_actfun activation_output; - - /* Total number of weights, and size of weights buffer. */ - int total_weights; - - /* Total number of neurons + inputs and size of output buffer. */ - int total_neurons; - - /* All weights (total_weights long). */ - double *weight; - - /* Stores input array and output of each neuron (total_neurons long). */ - double *output; - - /* Stores delta of each hidden and output neuron (total_neurons - inputs long). */ - double *delta; - -} genann; - - - -/* Creates and returns a new ann. */ -genann *genann_init(int inputs, int hidden_layers, int hidden, int outputs); - -/* Creates ANN from file saved with genann_write. */ -genann *genann_read(FILE *in); - -/* Sets weights randomly. Called by init. */ -void genann_randomize(genann *ann); - -/* Returns a new copy of ann. */ -genann *genann_copy(genann const *ann); - -/* Frees the memory used by an ann. */ -void genann_free(genann *ann); - -/* Runs the feedforward algorithm to calculate the ann's output. */ -double const *genann_run(genann const *ann, double const *inputs); - -/* Does a single backprop update. */ -void genann_train(genann const *ann, double const *inputs, double const *desired_outputs, double learning_rate); - -/* Saves the ann. */ -void genann_write(genann const *ann, FILE *out); - - -double genann_act_sigmoid(double a); -double genann_act_sigmoid_cached(double a); -double genann_act_threshold(double a); -double genann_act_linear(double a); - - -#ifdef __cplusplus -} -#endif - -#endif /*__GENANN_H__*/ diff --git a/main.c b/main.c index 1225686..573656e 100644 --- a/main.c +++ b/main.c @@ -8,7 +8,7 @@ #include #include -#include "genann.h" +#include "Genann.h" #define toss(t, n) ((t*) malloc((n) * sizeof(t))) @@ -21,10 +21,11 @@ typedef struct int icols; int ocols; int rows; + int split; } Data; -static int flns(FILE* const file) +static int lns(FILE* const file) { int ch = EOF; int lines = 0; @@ -41,7 +42,7 @@ static int flns(FILE* const file) return lines; } -static char* freadln(FILE* const file) +static char* readln(FILE* const file) { int ch = EOF; int reads = 0; @@ -65,13 +66,15 @@ static double** new2d(const int rows, const int cols) return row; } -static Data ndata(const int icols, const int ocols, const int rows) +static Data ndata(const int icols, const int ocols, const int rows, const double percentage) { - const Data data = { new2d(rows, icols), new2d(rows, ocols), icols, ocols, rows }; + const Data data = { + new2d(rows, icols), new2d(rows, ocols), icols, ocols, rows, rows * percentage + }; return data; } -static void dparse(const Data data, char* line, const int row) +static void parse(const Data data, char* line, const int row) { const int cols = data.icols + data.ocols; for(int col = 0; col < cols; col++) @@ -95,12 +98,11 @@ static void dfree(const Data d) free(d.od); } -static void shuffle(const Data d) +static void shuffle(const Data d, const int upper) { - srand(time(0)); - for(int a = 0; a < d.rows; a++) + for(int a = 0; a < upper; a++) { - const int b = rand() % d.rows; + const int b = rand() % d.split; double* ot = d.od[a]; double* it = d.id[a]; // Swap output. @@ -112,61 +114,82 @@ static void shuffle(const Data d) } } -static genann* dtrain(const Data d, const int ntimes, const int layers, const int neurons, const int rate) +static void print(const double* const arr, const int size) { - genann* const ann = genann_init(d.icols, layers, neurons, d.ocols); - for(int i = 0; i < ntimes; i++) - { - shuffle(d); - for(int j = 0; j < d.rows; j++) - genann_train(ann, d.id[j], d.od[j], rate); - printf("%f\n", (double) i / ntimes); - } - return ann; + for(int i = 0; i < size; i++) + printf("%d ", arr[i] > 0.9); } -static void dpredict(genann* ann, const Data d) +static int cmp(const double* const a, const double* const b, const int size) { - for(int i = 0; i < d.rows; i++) + for(int i = 0; i < size; i++) + { + const int aa = a[i] > 0.9; + const int bb = b[i] > 0.9; + if(aa != bb) + return 0; + } + return 1; +} + +static void predict(Genann* ann, const Data d) +{ + int matches = 0; + for(int i = d.split; i < d.rows; i++) { - printf("%d: ", i); // Prediciton. const double* const pred = genann_run(ann, d.id[i]); - for(int j = 0; j < d.ocols; j++) - printf("%s%d", j > 0 ? " " : "", pred[j] > 0.9); - printf("%s", " :: "); - // Actual. - for(int j = 0; j < d.ocols; j++) - printf("%s%d", j > 0 ? " " : "", (int) d.od[i][j]); - putchar('\n'); + const double* const real = d.od[i]; + print(pred, d.ocols); + printf(":: "); + print(real, d.ocols); + const int match = cmp(pred, real, d.ocols); + printf("-> %d\n", match); + matches += match; } + printf("%f\n", (double) matches / (d.rows - d.split)); } -static Data dbuild(char* path, const int icols, const int ocols) +static Data build(char* path, const int icols, const int ocols, const double percentage) { FILE* file = fopen(path, "r"); - const int rows = flns(file); - Data data = ndata(icols, ocols, rows); + const int rows = lns(file); + Data data = ndata(icols, ocols, rows, percentage); for(int row = 0; row < rows; row++) { - char* line = freadln(file); - dparse(data, line, row); + char* line = readln(file); + parse(data, line, row); free(line); } fclose(file); return data; } +static Genann* train(const Data d, const int ntimes, const int layers, const int neurons, const double rate) +{ + Genann* const ann = genann_init(d.icols, layers, neurons, d.ocols); + double annealed = rate; + for(int i = 0; i < ntimes; i++) + { + shuffle(d, d.split); + for(int j = 0; j < d.split; j++) + genann_train(ann, d.id[j], d.od[j], annealed); + printf("%f: %f\n", (double) i / ntimes, annealed); + annealed *= 0.95; + } + return ann; +} + int main(int argc, char* argv[]) { (void) argc; (void) argv; - const Data test = dbuild("semeion.data", 256, 10); - const Data vald = dbuild("written.data", 256, 10); - genann* ann = dtrain(test, 256, 1, 128, 1); - dpredict(ann, vald); + srand(time(0)); + const Data data = build("semeion.data", 256, 10, 0.9); + shuffle(data, data.rows); + Genann* ann = train(data, 128, 1, data.icols / 2.0, 3.0); // Hyperparams. + predict(ann, data); genann_free(ann); - dfree(test); - dfree(vald); + dfree(data); return 0; }