Files
genann/test.c

385 lines
9.1 KiB
C

/*
* GENANN - Minimal C Artificial Neural Network
*
* Copyright (c) 2015-2018 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 "minctest.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
void basic() {
genann *ann = genann_init(1, 0, 0, 1);
lequal(ann->total_weights, 2);
double a;
a = 0;
ann->weight[0] = 0;
ann->weight[1] = 0;
lfequal(0.5, *genann_run(ann, &a));
a = 1;
lfequal(0.5, *genann_run(ann, &a));
a = 11;
lfequal(0.5, *genann_run(ann, &a));
a = 1;
ann->weight[0] = 1;
ann->weight[1] = 1;
lfequal(0.5, *genann_run(ann, &a));
a = 10;
ann->weight[0] = 1;
ann->weight[1] = 1;
lfequal(1.0, *genann_run(ann, &a));
a = -10;
lfequal(0.0, *genann_run(ann, &a));
genann_free(ann);
}
void xor() {
genann *ann = genann_init(2, 1, 2, 1);
ann->activation_hidden = genann_act_threshold;
ann->activation_output = genann_act_threshold;
lequal(ann->total_weights, 9);
/* First hidden. */
ann->weight[0] = .5;
ann->weight[1] = 1;
ann->weight[2] = 1;
/* Second hidden. */
ann->weight[3] = 1;
ann->weight[4] = 1;
ann->weight[5] = 1;
/* Output. */
ann->weight[6] = .5;
ann->weight[7] = 1;
ann->weight[8] = -1;
double input[4][2] = {{0, 0}, {0, 1}, {1, 0}, {1, 1}};
double output[4] = {0, 1, 1, 0};
lfequal(output[0], *genann_run(ann, input[0]));
lfequal(output[1], *genann_run(ann, input[1]));
lfequal(output[2], *genann_run(ann, input[2]));
lfequal(output[3], *genann_run(ann, input[3]));
genann_free(ann);
}
void backprop() {
genann *ann = genann_init(1, 0, 0, 1);
double input, output;
input = .5;
output = 1;
double first_try = *genann_run(ann, &input);
genann_train(ann, &input, &output, .5);
double second_try = *genann_run(ann, &input);
lok(fabs(first_try - output) > fabs(second_try - output));
genann_free(ann);
}
void train_and() {
double input[4][2] = {{0, 0}, {0, 1}, {1, 0}, {1, 1}};
double output[4] = {0, 0, 0, 1};
genann *ann = genann_init(2, 0, 0, 1);
int i, j;
for (i = 0; i < 50; ++i) {
for (j = 0; j < 4; ++j) {
genann_train(ann, input[j], output + j, .8);
}
}
ann->activation_output = genann_act_threshold;
lfequal(output[0], *genann_run(ann, input[0]));
lfequal(output[1], *genann_run(ann, input[1]));
lfequal(output[2], *genann_run(ann, input[2]));
lfequal(output[3], *genann_run(ann, input[3]));
genann_free(ann);
}
void train_or() {
double input[4][2] = {{0, 0}, {0, 1}, {1, 0}, {1, 1}};
double output[4] = {0, 1, 1, 1};
genann *ann = genann_init(2, 0, 0, 1);
genann_randomize(ann);
int i, j;
for (i = 0; i < 50; ++i) {
for (j = 0; j < 4; ++j) {
genann_train(ann, input[j], output + j, .8);
}
}
ann->activation_output = genann_act_threshold;
lfequal(output[0], *genann_run(ann, input[0]));
lfequal(output[1], *genann_run(ann, input[1]));
lfequal(output[2], *genann_run(ann, input[2]));
lfequal(output[3], *genann_run(ann, input[3]));
genann_free(ann);
}
void train_xor() {
double input[4][2] = {{0, 0}, {0, 1}, {1, 0}, {1, 1}};
double output[4] = {0, 1, 1, 0};
genann *ann = genann_init(2, 1, 2, 1);
int i, j;
for (i = 0; i < 500; ++i) {
for (j = 0; j < 4; ++j) {
genann_train(ann, input[j], output + j, 3);
}
/* printf("%1.2f ", xor_score(ann)); */
}
ann->activation_output = genann_act_threshold;
lfequal(output[0], *genann_run(ann, input[0]));
lfequal(output[1], *genann_run(ann, input[1]));
lfequal(output[2], *genann_run(ann, input[2]));
lfequal(output[3], *genann_run(ann, input[3]));
genann_free(ann);
}
#ifndef genann_act
void train_xor_act(genann_actfun act) {
double input[4][2] = {{0, 0}, {0, 1}, {1, 0}, {1, 1}};
double output[4] = {0, 1, 1, 0};
int i, j, r;
int solved = 0;
/* An unlucky starting position can strand training (e.g. dead relu
* units), so allow a few restarts. */
for (r = 0; r < 10 && !solved; ++r) {
genann *ann = genann_init(2, 1, 4, 1);
ann->activation_hidden = act;
for (i = 0; i < 2000; ++i) {
for (j = 0; j < 4; ++j) {
genann_train(ann, input[j], output + j, .5);
}
}
solved = 1;
for (j = 0; j < 4; ++j) {
if ((*genann_run(ann, input[j]) > .5) != (output[j] > .5)) solved = 0;
}
genann_free(ann);
}
lok(solved);
}
void train_xor_tanh() {
train_xor_act(genann_act_tanh);
}
void train_xor_relu() {
train_xor_act(genann_act_relu);
}
void gradient_act(genann_actfun hidden, genann_actfun output) {
double input[2] = {.3, -.2};
double target[1] = {.7};
const double eps = 1e-6;
const double rate = .1;
double checked = 0;
int i;
genann *ann = genann_init(2, 1, 3, 1);
ann->activation_hidden = hidden;
ann->activation_output = output;
/* Fixed, varied weights keep this test deterministic. */
for (i = 0; i < ann->total_weights; ++i) {
ann->weight[i] = sin(i * 1.7) * .5;
}
genann *trained = genann_copy(ann);
genann_train(trained, input, target, rate);
/* Each weight update must match the central-difference gradient of
* the squared error E = (target - out)^2 / 2. */
for (i = 0; i < ann->total_weights; ++i) {
const double save = ann->weight[i];
double o, e1, e2, numeric;
ann->weight[i] = save + eps;
o = *genann_run(ann, input);
e1 = .5 * (target[0] - o) * (target[0] - o);
ann->weight[i] = save - eps;
o = *genann_run(ann, input);
e2 = .5 * (target[0] - o) * (target[0] - o);
ann->weight[i] = save;
numeric = (e1 - e2) / (2 * eps);
checked += fabs(numeric);
lok(fabs((trained->weight[i] - save) - (-rate * numeric)) < 1e-7);
}
/* Guard against passing trivially with an all-zero gradient. */
lok(checked > .001);
genann_free(ann);
genann_free(trained);
}
void gradient_tanh() {
gradient_act(genann_act_tanh, genann_act_tanh);
}
void gradient_relu() {
gradient_act(genann_act_relu, genann_act_sigmoid);
}
#endif
void persist() {
genann *first = genann_init(1000, 5, 50, 10);
FILE *out = fopen("persist.txt", "w");
genann_write(first, out);
fclose(out);
FILE *in = fopen("persist.txt", "r");
genann *second = genann_read(in);
fclose(in);
lequal(first->inputs, second->inputs);
lequal(first->hidden_layers, second->hidden_layers);
lequal(first->hidden, second->hidden);
lequal(first->outputs, second->outputs);
lequal(first->total_weights, second->total_weights);
int i;
for (i = 0; i < first->total_weights; ++i) {
lok(first->weight[i] == second->weight[i]);
}
genann_free(first);
genann_free(second);
}
void copy() {
genann *first = genann_init(1000, 5, 50, 10);
genann *second = genann_copy(first);
lequal(first->inputs, second->inputs);
lequal(first->hidden_layers, second->hidden_layers);
lequal(first->hidden, second->hidden);
lequal(first->outputs, second->outputs);
lequal(first->total_weights, second->total_weights);
int i;
for (i = 0; i < first->total_weights; ++i) {
lfequal(first->weight[i], second->weight[i]);
}
genann_free(first);
genann_free(second);
}
void sigmoid() {
double i = -20;
const double max = 20;
const double d = .0001;
while (i < max) {
lfequal(genann_act_sigmoid(NULL, i), genann_act_sigmoid_cached(NULL, i));
i += d;
}
}
int main(int argc, char *argv[])
{
printf("GENANN TEST SUITE\n");
srand(100); //Repeatable test results.
lrun("basic", basic);
lrun("xor", xor);
lrun("backprop", backprop);
lrun("train and", train_and);
lrun("train or", train_or);
lrun("train xor", train_xor);
#ifndef genann_act
lrun("train tanh", train_xor_tanh);
lrun("train relu", train_xor_relu);
lrun("gradient tanh", gradient_tanh);
lrun("gradient relu", gradient_relu);
#endif
lrun("persist", persist);
lrun("copy", copy);
lrun("sigmoid", sigmoid);
lresults();
return lfails != 0;
}