This commit is contained in:
Gustav Louw 2018-03-26 14:11:15 -07:00
commit ed310b24dd
5 changed files with 682 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.dat*
*.o
*.d
shaper

62
Makefile Normal file
View File

@ -0,0 +1,62 @@
CC = gcc
NAME = shaper
SRCS =
SRCS+= main.c
SRCS+= genann.c
# CompSpec defined in windows environment.
ifdef ComSpec
BIN = $(NAME).exe
else
BIN = $(NAME)
endif
CFLAGS =
ifdef ComSpec
CFLAGS += -I ../SDL2-2.0.7/i686-w64-mingw32/include
endif
CFLAGS += -std=gnu99
CFLAGS += -Wshadow -Wall -Wpedantic -Wextra -Wdouble-promotion -Wunused-result
CFLAGS += -g
CFLAGS += -Ofast -march=native -pipe
CFLAGS += -flto
LDFLAGS =
ifdef ComSpec
LDFLAGS += -L..\SDL2-2.0.7\i686-w64-mingw32\lib
LDFLAGS += -lmingw32
LDFLAGS += -lSDL2main
endif
LDFLAGS += -lSDL2 -lm
ifdef ComSpec
RM = del /F /Q
MV = ren
else
RM = rm -f
MV = mv -f
endif
# Link.
$(BIN): $(SRCS:.c=.o)
@echo $(CC) *.o -o $(BIN)
@$(CC) $(CFLAGS) $(SRCS:.c=.o) $(LDFLAGS) -o $(BIN)
# Compile.
%.o : %.c Makefile
@echo $(CC) -c $*.c
@$(CC) $(CFLAGS) -MMD -MP -MT $@ -MF $*.td -c $<
@$(RM) $*.d
@$(MV) $*.td $*.d
%.d: ;
-include *.d
clean:
$(RM) vgcore.*
$(RM) cachegrind.out.*
$(RM) callgrind.out.*
$(RM) $(BIN)
$(RM) $(SRCS:.c=.o)
$(RM) $(SRCS:.c=.d)

364
genann.c Normal file
View File

@ -0,0 +1,364 @@
/*
* 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 <assert.h>
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.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;
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]);
}
}

110
genann.h Normal file
View File

@ -0,0 +1,110 @@
/*
* 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 <stdio.h>
#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__*/

142
main.c Normal file
View File

@ -0,0 +1,142 @@
// This program uses the Genann Neural Network Library to learn hand written digits.
//
// Get it from the machine learning database:
// wget http://archive.ics.uci.edu/ml/machine-learning-databases/semeion/semeion.data
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "genann.h"
#define toss(t, n) ((t*) malloc((n) * sizeof(t)))
#define retoss(ptr, t, n) (ptr = (t*) realloc((ptr), (n) * sizeof(t)))
typedef struct
{
double** id;
double** od;
int icols;
int ocols;
int rows;
}
Data;
static int flns(FILE* const file)
{
int ch = EOF;
int lines = 0;
int pc = '\n';
while((ch = getc(file)) != EOF)
{
if(ch == '\n')
lines++;
pc = ch;
}
if(pc != '\n')
lines++;
rewind(file);
return lines;
}
static char* freadln(FILE* const file)
{
int ch = EOF;
int reads = 0;
int size = 128;
char* line = toss(char, size);
while((ch = getc(file)) != '\n' && ch != EOF)
{
line[reads++] = ch;
if(reads + 1 == size)
retoss(line, char, size *= 2);
}
line[reads] = '\0';
return line;
}
static double** new2d(const int rows, const int cols)
{
double** row = toss(double*, rows);
for(int r = 0; r < rows; r++)
row[r] = toss(double, cols);
return row;
}
static Data ndata(const int icols, const int ocols, const int rows)
{
const Data data = { new2d(rows, icols), new2d(rows, ocols), icols, ocols, rows };
return data;
}
static void dparse(const Data data, char* line, const int row)
{
const int cols = data.icols + data.ocols;
for(int col = 0; col < cols; col++)
{
const float val = atof(strtok(col == 0 ? line : NULL, " "));
if(col < data.icols)
data.id[row][col] = val;
else
data.od[row][col - data.icols] = val;
}
}
static void dfree(const Data d)
{
for(int row = 0; row < d.rows; row++)
{
free(d.id[row]);
free(d.od[row]);
}
free(d.id);
free(d.od);
}
static genann* dtrain(const Data d, const int ntimes, const int layers, const int neurons, const int rate)
{
genann* const ann = genann_init(d.icols, layers, neurons, d.ocols);
for(int i = 0; i < ntimes; i++)
for(int j = 0; j < d.rows; j++)
genann_train(ann, d.id[j], d.od[j], rate);
return ann;
}
static void dpredict(genann* ann, const Data d)
{
for(int i = 0; i < d.rows; i++)
{
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);
putchar('\n');
}
}
static Data dbuild(char* path, const int icols, const int ocols)
{
FILE* file = fopen(path, "r");
const int rows = flns(file);
Data data = ndata(icols, ocols, rows);
for(int row = 0; row < rows; row++)
{
char* line = freadln(file);
dparse(data, line, row);
free(line);
}
fclose(file);
return data;
}
int main(int argc, char* argv[])
{
(void) argc;
(void) argv;
const Data data = dbuild("semeion.data", 256, 10);
genann* ann = dtrain(data, 256, 1, 32, 1);
dpredict(ann, data);
genann_free(ann);
dfree(data);
return 0;
}