summaryrefslogtreecommitdiffstats
path: root/include/ann.h
diff options
context:
space:
mode:
Diffstat (limited to 'include/ann.h')
-rwxr-xr-xinclude/ann.h582
1 files changed, 582 insertions, 0 deletions
diff --git a/include/ann.h b/include/ann.h
new file mode 100755
index 0000000..087daa5
--- /dev/null
+++ b/include/ann.h
@@ -0,0 +1,582 @@
+// ann.h - Artificial Neural Nework Libray
+
+#ifndef ANN_H
+#define ANN_H
+
+#include <stdint.h>
+#include "./type.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ IDENTITY,
+ BINARY,
+ SIGMOID,
+ RELU,
+ ELU,
+ LRELU,
+ TANH,
+} ann_activation_t;
+
+typedef struct {
+ // The full size of the allocated structure
+ uint_t n;
+
+ // The number of layers in the neural network
+ uint_t layer_n;
+
+ // The neuron count ( hidden )
+ uint_t neuron_n;
+
+ // The total number of weights and biases
+ uint_t weight_n;
+
+ // The number of neurons in each layer
+ uint_t *layer_neuron_n;
+
+ // The pointer to the neurons
+ fp_t *neuron;
+
+ // The weights and biases for each neuron
+ // - w_lji denotes the connection between the jth neuron in layer l, and
+ // the ith neuron in the previous layer
+ // - b_lj denotes the bias for the jth neuron in layer l
+ // { w_000, w_001, w_002, .... , b_00, ...., w_ijk, b_ij }`
+ fp_t *weight;
+
+ // The delta between between the actual and the cost function
+ fp_t *delta;
+
+ // The activation function used in the hidden layer neurons
+ fp_t (*activation_hidden)(fp_t);
+
+ // The partial derivative of the activation function used in the hidden
+ // layer neurons
+ fp_t (*activation_hidden_partial)(fp_t);
+
+ // The activation function used in the ouput neurons
+ fp_t (*activation_output)(fp_t);
+
+ // The partial derivative of the activation function used in the output
+ // layer neurons
+ fp_t (*activation_output_partial)(fp_t);
+} ann_t;
+
+ann_t *ann_init(uint_t, uint_t *);
+ann_t *ann_copy(ann_t const *);
+void ann_free(ann_t *);
+void ann_random(ann_t *);
+
+void ann_propagation_forward(ann_t *, fp_t const *const, fp_t *);
+void ann_propagation_backward(ann_t *, fp_t const *, fp_t *, fp_t const *,
+ fp_t);
+void ann_train_numeric(ann_t *, fp_t const *, fp_t const *, fp_t);
+
+fp_t ann_error_total(fp_t const *, fp_t const *, uint_t);
+void ann_set_activation(ann_t *, ann_activation_t, ann_activation_t);
+
+void ann_print_weight(ann_t *);
+void ann_print_neuron(ann_t *, fp_t const *const, fp_t const *const);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANN_H
+
+////////////////////////////////////////////////////////////////////////////////
+// IMPELEMENTATION SECTION
+////////////////////////////////////////////////////////////////////////////////
+
+#ifdef ANN_IMPLEMENTATION
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <tgmath.h>
+
+#define PRINT_PRECISION 10
+
+#define SQUARE_ROOT_2 1.4142135623730950488016887242096
+#define PI 3.1415926535897932384626433832795
+#define SQUARE_ROOT_PI 1.7724538509055160272981674833411
+#define SQUARE_ROOT_2_OVER_PI 0.7978845608028653558798921198687
+
+#define ELU_ALPHA 0.2
+#define LRELU_ALPHA 0.2
+
+static fp_t ann_random_range(fp_t, fp_t);
+
+static fp_t ann_error(fp_t, fp_t);
+static fp_t ann_error_partial(fp_t, fp_t);
+
+static fp_t ann_activation_identity(fp_t);
+static fp_t ann_activation_identity_partial(fp_t);
+static fp_t ann_activation_binary(fp_t);
+static fp_t ann_activation_binary_partial(fp_t);
+static fp_t ann_activation_sigmoid(fp_t);
+static fp_t ann_activation_sigmoid_partial(fp_t);
+static fp_t ann_activation_relu(fp_t);
+static fp_t ann_activation_relu_partial(fp_t);
+static fp_t ann_activation_elu(fp_t);
+static fp_t ann_activation_elu_partial(fp_t);
+static fp_t ann_activation_lrelu(fp_t);
+static fp_t ann_activation_lrelu_partial(fp_t);
+static fp_t ann_activation_tanh(fp_t);
+static fp_t ann_activation_tanh_partial(fp_t);
+
+static fp_t (*ACTIVATION[][2])(fp_t) = {
+ {ann_activation_identity, ann_activation_identity_partial}, // IDENTITY
+ {ann_activation_binary, ann_activation_binary_partial}, // BINARY
+ {ann_activation_sigmoid, ann_activation_sigmoid_partial}, // SIGMOID
+ {ann_activation_relu, ann_activation_relu_partial}, // RELU
+ {ann_activation_elu, ann_activation_elu_partial}, // ELU
+ {ann_activation_lrelu, ann_activation_lrelu_partial}, // LRELU
+ {ann_activation_tanh, ann_activation_tanh_partial}, // TANH
+};
+
+// ann_init()
+//
+// Initialize the neural network
+//
+// layer_n - The layer count for the layer count
+// layer_neuron_n - An array of neuron counts for each layer
+//
+// return - The created ann_t instance
+
+ann_t *ann_init(uint_t layer_n, uint_t *layer_neuron_n) {
+ assert(layer_n >= 2);
+
+ uint_t neuron_n = 0;
+ uint_t weight_n = 0;
+
+ // Calculate the number of neurons and weights / biases
+ uint_t l = 1;
+ for (; l < layer_n - 1; l++) {
+ neuron_n += layer_neuron_n[l];
+ weight_n += layer_neuron_n[l] * (layer_neuron_n[l - 1] + 1);
+ }
+
+ weight_n += layer_neuron_n[l] * (layer_neuron_n[l - 1] + 1);
+
+ uint_t n =
+ sizeof(ann_t) + // ANN
+ (sizeof(uint_t) * layer_n) + // layer_neuron_n[]
+ (sizeof(fp_t) * (neuron_n + // neuron[]
+ weight_n + // weight[]
+ neuron_n + layer_neuron_n[layer_n - 1])); // delta[]
+
+ // Allocate everything as one structure
+ ann_t *ann = (ann_t *)malloc(n);
+
+ // ann_t | layer_neuron_n[] | neuron[] | weight[] | delta[]
+ ann->n = n;
+ ann->layer_n = layer_n;
+ ann->weight_n = weight_n;
+ ann->neuron_n = neuron_n;
+ ann->layer_neuron_n = (uint_t *)((uint8_t *)ann + sizeof(ann_t));
+ memcpy(ann->layer_neuron_n, layer_neuron_n, sizeof(uint_t) * layer_n);
+ ann->neuron = (fp_t *)(ann->layer_neuron_n + ann->layer_n);
+ ann->weight = ann->neuron + ann->neuron_n;
+ ann->delta = ann->weight + ann->weight_n;
+
+ ann_set_activation(ann, SIGMOID, SIGMOID);
+
+ return ann;
+}
+
+// ann_free()
+//
+// Free the neural network's memory
+//
+// ann - The instance of ann_t to free
+
+void ann_free(ann_t *ann) { free(ann); }
+
+// ann_copy()
+//
+// Make a copy of the given ann_t instance
+//
+// ann - The instance of ann_t to copy
+//
+// return - The copied ann_t instance
+
+ann_t *ann_copy(ann_t const *ann) {
+ ann_t *copy = (ann_t *)malloc(ann->n);
+ memcpy(copy, ann, ann->n);
+
+ return copy;
+}
+
+// ann_propagation_forward()
+//
+// Perform forward propagation on the ann_t instance
+//
+// ann - The ann_t instance to perform the propagation on
+// input - An array containing the input vector
+// output - The destination array for the output vector
+
+void ann_propagation_forward(ann_t *ann, fp_t const *const input,
+ fp_t *output) {
+ // printf("ann:\t\t%p\n", ann);
+ // printf("output:\t\t%p\n", output);
+ fp_t *w_ij = ann->weight;
+ // printf("ann->weight:\t%p\n", w_ij);
+ fp_t const *x = input; // Input neuron into y
+ fp_t *y = ann->neuron; // The current neuron being calculated
+
+ uint_t l = 1;
+
+ for (; l < ann->layer_n - 1; l++) {
+ for (uint_t j = 0; j < ann->layer_neuron_n[l]; j++) {
+ // printf("neuron[%u]:\t%p\n", j, &(y[j]));
+ y[j] = 0;
+ for (uint_t i = 0; i < ann->layer_neuron_n[l - 1]; i++) {
+ y[j] += x[i] * *w_ij++;
+ }
+
+ y[j] += *w_ij++;
+ y[j] = ann->activation_hidden(y[j]);
+ }
+
+ x = y;
+ y += ann->layer_neuron_n[l];
+ }
+
+ // Last layer
+ for (uint_t j = 0; j < ann->layer_neuron_n[l]; j++) {
+ // printf("output[%u]:\t%p\n", j, &(output[j]));
+ output[j] = 0;
+
+ for (uint_t i = 0; i < ann->layer_neuron_n[l - 1]; i++) {
+ output[j] += x[i] * *w_ij++;
+ }
+
+ output[j] += *w_ij++;
+ output[j] = ann->activation_output(output[j]);
+ }
+}
+
+// ann_propagation_backward()
+//
+// Perform backpropagation on the ann_t instance
+//
+// ann - The ann_t instance to perform backpropagation upon
+// input - Input vector array
+// output - Output vector array
+// target - Target output vector array
+// rate - Learning rate
+
+void ann_propagation_backward(ann_t *ann, fp_t const *input, fp_t *output,
+ fp_t const *target, fp_t rate) {
+ int_t l = ann->layer_n - 1;
+ uint_t i, j, q;
+
+ // First output layer delta
+ fp_t *d_j = ann->delta + ann->neuron_n;
+
+ // Output Deltas
+ for (j = 0; j < ann->layer_neuron_n[l]; j++) {
+ d_j[j] = ann->activation_output_partial(output[j]) *
+ ann_error_partial(output[j], target[j]);
+ }
+
+ // First weight in the set between the last layer and the current
+ fp_t *w_jq = ann->weight + ann->weight_n -
+ ann->layer_neuron_n[l] * ann->layer_neuron_n[l - 1] -
+ ann->layer_neuron_n[l];
+
+ fp_t *o_j = ann->neuron + ann->neuron_n;
+ fp_t *d_q;
+
+ l--;
+
+ // Hidden Deltas
+ for (; l > 0; --l) {
+ d_q = d_j;
+ d_j -= ann->layer_neuron_n[l];
+ o_j -= ann->layer_neuron_n[l];
+
+ for (j = 0; j < ann->layer_neuron_n[l]; j++) {
+ d_j[j] = 0;
+
+ for (q = 0; q < ann->layer_neuron_n[l + 1]; q++) {
+ d_j[j] += w_jq[q * (ann->layer_neuron_n[l] + 1) + j] * d_q[q];
+ }
+
+ d_j[j] *= ann->activation_hidden_partial(o_j[j]);
+ }
+
+ w_jq -= (ann->layer_neuron_n[l - 1] * ann->layer_neuron_n[l] +
+ ann->layer_neuron_n[l - 1]);
+ }
+
+ fp_t *w_ij = ann->weight;
+ d_j = ann->delta;
+
+ l = 1;
+
+ // Input training
+ for (j = 0; j < ann->layer_neuron_n[l]; j++) {
+ for (i = 0; i < ann->layer_neuron_n[l - 1]; i++) {
+ *w_ij -= rate * input[i] * d_j[j];
+ w_ij++;
+ }
+
+ *w_ij -= rate * d_j[j];
+ w_ij++;
+ }
+
+ l++;
+ fp_t *i_i = ann->neuron;
+
+ // Hidden training
+ for (; l < (int_t)ann->layer_n; l++) {
+ d_j += ann->layer_neuron_n[l - 1];
+
+ for (j = 0; j < ann->layer_neuron_n[l]; j++) {
+ for (i = 0; i < ann->layer_neuron_n[l - 1]; i++) {
+ *w_ij -= rate * i_i[i] * d_j[j];
+ w_ij++;
+ }
+
+ *w_ij -= rate * d_j[j];
+ w_ij++;
+ }
+
+ i_i += ann->layer_neuron_n[l - 1];
+ }
+}
+
+// ann_activation_identity()
+//
+// y = x
+// dy/dx = 1
+
+static fp_t ann_activation_identity(fp_t x) { return x; }
+static fp_t ann_activation_identity_partial(fp_t x) { return 1; }
+
+// ann_activation_binary()
+//
+// y = { x < 0 -> 0, x > 0 -> 1 }
+// dy/dx = 0
+
+static fp_t ann_activation_binary(fp_t x) {
+ return (x > 0.0) ? 1.0 : (x < 0.0) ? -1.0 : 0.0;
+}
+static fp_t ann_activation_binary_partial(fp_t x) { return 0.0; }
+
+// ann_activation_sign()
+//
+// y = { x < 0 -> -1, x = 0 -> 0, x > 0 -> 1 }
+// dy/dx = 0
+
+static fp_t ann_activation_sign(fp_t x) { return (x > 0.0) ? 1.0 : 0.0; }
+static fp_t ann_activation_sign_partial(fp_t x) { return 0.0; }
+
+// ann_activation_sigmoid()
+//
+// y = 1.0 / ( 1.0 + e^-x )
+// dy/dx = f(x) * ( 1.0 - f(x) )
+
+static fp_t ann_activation_sigmoid(fp_t x) { return 1.0 / (1.0 + exp(-x)); }
+static fp_t ann_activation_sigmoid_partial(fp_t x) { return x * (1.0 - x); }
+
+// ann_activation_relu()
+//
+// y = { x < 0 -> 0, x >= 0 -> x }
+// dy/dx = { x < 0 -> 0, x >= 0 -> 1 }
+
+static fp_t ann_activation_relu(fp_t x) { return (x > 0.0) ? x : 0.0; }
+static fp_t ann_activation_relu_partial(fp_t x) {
+ return (x > 0.0) ? 1.0 : 0.0;
+}
+
+// ann_activation_elu()
+//
+// y = { x < 0 -> a * ( e^x - 1 ), x >= 0 -> x }
+// dy/dx = { x < 0 -> x + a, x >= 0 -> 1 }
+
+static fp_t ann_activation_elu(fp_t x) {
+ return (x > 0.0) ? x : ELU_ALPHA * (expm1(x));
+}
+static fp_t ann_activation_elu_partial(fp_t x) {
+ return (x > 0.0) ? 1.0 : x + ELU_ALPHA;
+}
+
+// ann_activation_lelu()
+//
+// y = { x < 0 -> a * x, x >= 0 -> x }
+// dy/dx = { x < 0 -> a, x >= 0 -> 1 }
+
+static fp_t ann_activation_lrelu(fp_t x) {
+ return (x > 0.0) ? x : LRELU_ALPHA * x;
+}
+static fp_t ann_activation_lrelu_partial(fp_t x) {
+ return (x > 0.0) ? 1.0 : LRELU_ALPHA;
+}
+
+// ann_activation_tanh()
+//
+// y = sinh( x ) / cosh( x ) = ( e^2x - 1 ) / ( e^2x + 1 )
+// dy/dx = 1.0 - f(x)^2
+
+static fp_t ann_activation_tanh(fp_t x) { return tanh(x); }
+static fp_t ann_activation_tanh_partial(fp_t x) { return 1.0 - (x * x); }
+
+// ann_error()
+//
+// Implementation of mean squared error for calculating the difference between
+// the current and target outputs for the ann_t instance
+//
+// output - The current ann_t output
+// target - The target ann_t output
+
+fp_t ann_error(fp_t output, fp_t target) {
+ fp_t error = output - target;
+ error = 0.5 * error * error;
+
+ return error;
+}
+
+// ann_error_partial()
+//
+// The partial derivative of mean squared error where n = 2
+//
+// output - The current ann_t output
+// target - The target ann_t output
+
+fp_t ann_error_partial(fp_t output, fp_t target) { return (output - target); }
+
+// ann_error_total()
+//
+//
+//
+// output - The array of current outputs
+// target - The array of target outputs
+// n - The size of the output and target arrays
+
+fp_t ann_error_total(fp_t const *output, fp_t const *target, uint_t n) {
+ fp_t error = 0;
+ for (uint_t i = 0; i < n; i++) {
+ error += ann_error(output[i], target[i]);
+ }
+
+ return error;
+}
+
+// ann_set_activation()
+//
+// Set the activation functions for the hidden and output layers
+//
+// ann - The current ann_t instance
+// activation_hidden - The hidden layer activation function
+// activation_output - The output layer activation function
+
+void ann_set_activation(ann_t *ann, ann_activation_t activation_hidden,
+ ann_activation_t activation_output) {
+ ann->activation_hidden = ACTIVATION[activation_hidden][0];
+ ann->activation_hidden_partial = ACTIVATION[activation_hidden][1];
+ ann->activation_output = ACTIVATION[activation_output][0];
+ ann->activation_output_partial = ACTIVATION[activation_output][1];
+}
+
+// ann_random()
+//
+// Set the weights of the ann_t instance to random numbers in the [-1, 1] range
+//
+// ann - The current ann_t instance
+
+void ann_random(ann_t *ann) {
+ uint_t l, j, i;
+ fp_t *wb = ann->weight;
+ for (l = 1; l < ann->layer_n; l++) {
+ for (j = 0; j < ann->layer_neuron_n[l]; j++) {
+ i = 0;
+ for (; i < ann->layer_neuron_n[l - 1]; i++) {
+ *wb++ = ann_random_range(-1.0, 1.0);
+ }
+
+ *wb++ = 0;
+ }
+ }
+}
+
+// ann_random_range()
+//
+// Remap a random value to a given range
+//
+// low - The lower bounds of the range
+// high - The upper bounds of the range
+
+static fp_t ann_random_range(fp_t low, fp_t high) {
+ return (low + ((fp_t)rand()) * (high - low) / RAND_MAX);
+}
+
+// ann_print_neuron()
+//
+// Print a text representation of the neurons in a given ann_t instance
+//
+// ann - The current ann_t instance
+// input - The input used for the ann_t propagation being printed
+// output - The output used for the ann_t propagation being printed
+
+void ann_print_neuron(ann_t *ann, fp_t const *const input,
+ fp_t const *const output) {
+ fputs("-> ", stderr);
+ for (uint_t i = 0; i < ann->layer_neuron_n[0]; i++) {
+ fprintf(stderr, " %+.*f", PRINT_PRECISION, input[i]);
+ }
+
+ fputs("\n", stderr);
+
+ fp_t *n = ann->neuron;
+
+ for (uint_t l = 1; l < ann->layer_n - 1; l++) {
+ fprintf(stderr, " ", l);
+ for (uint_t i = 0; i < ann->layer_neuron_n[l]; i++) {
+ fprintf(stderr, " %+.*f", PRINT_PRECISION, *n);
+ n++;
+ }
+
+ fputs("\n", stderr);
+ }
+
+ fputs("= ", stderr);
+ for (uint_t i = 0; i < ann->layer_neuron_n[ann->layer_n - 1]; i++) {
+ fprintf(stderr, " %+.*f", PRINT_PRECISION, output[i]);
+ }
+
+ fputs("\n\n", stderr);
+}
+
+// ann_print_weight()
+//
+// Print a text representation of weights in the given ann_t instance
+//
+// ann - The current ann_t instance
+
+void ann_print_weight(ann_t *ann) {
+ fp_t *weight = ann->weight;
+
+ for (uint_t i = 1; i < ann->layer_n; i++) {
+ for (uint_t j = 0; j < ann->layer_neuron_n[i - 1] + 1; j++) {
+ for (uint_t k = 0; k < ann->layer_neuron_n[i]; k++) {
+ fprintf(stderr, " %+.*f", PRINT_PRECISION,
+ *(weight + j + k * (ann->layer_neuron_n[i - 1] + 1)));
+ }
+
+ fputs("\n", stderr);
+ }
+
+ weight += ann->layer_neuron_n[i - 1] * ann->layer_neuron_n[i] +
+ ann->layer_neuron_n[i];
+
+ fputs("\n", stderr);
+ }
+}
+
+#endif // ANN_IMPLEMENTATION \ No newline at end of file