123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- /*
- * 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 <stdlib.h>
- #include <string.h>
- #include <math.h>
- #include <assert.h>
- #include <stdio.h>
- #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;
- fscanf(in, "%d %d %d %d", &inputs, &hidden_layers, &hidden, &outputs);
- genann *ann = genann_init(inputs, hidden_layers, hidden, outputs);
- int i;
- for (i = 0; i < ann->total_weights; ++i) {
- fscanf(in, " %le", ann->weight + i);
- }
- 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 = 0;
- for (k = 0; k < (h == 0 ? ann->inputs : ann->hidden) + 1; ++k) {
- if (k == 0) {
- sum += *w++ * -1.0;
- } else {
- sum += *w++ * i[k-1];
- }
- }
- *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 = 0;
- for (k = 0; k < (ann->hidden_layers ? ann->hidden : ann->inputs) + 1; ++k) {
- if (k == 0) {
- sum += *w++ * -1.0;
- } else {
- sum += *w++ * i[k-1];
- }
- }
- *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]);
- }
- }
|