/* genetic_algorithm.c -- This belongs to gneural_network
gneural_network is the GNU package which implements a programmable neural network.
Copyright (C) 2016 Jean Michel Sellier
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
// performs a genetic search of the best weights during the training process
// initially implemented by : Jean Michel Sellier
// drastically improved by : Nan
#include "genetic_algorithm.h"
#ifdef _OPENMP
#include
#define PAR_QSORT_LOW_LIMIT 1024
void memswap(void* a, void* b, size_t size) {
// suppose a and b never overlapped.
if (a == b) return;
void* t = malloc(size);
memcpy(t, a, size);
memcpy(a, b, size);
memcpy(b, t, size);
free(t);
}
void* partition(void* data, size_t element_count, size_t element_size, __compar_fn_t cmp_func) {
void* pivot = data + (element_count - 1) * element_size;
void* i = data;
void* j = data;
while (j < pivot) {
if (cmp_func(j, pivot) < 0) {
memswap(i, j, element_size);
i += element_size;
}
j += element_size;
}
memswap(i, pivot, element_size);
return i;
}
void par_qsort(void* data, size_t element_count, size_t element_size, __compar_fn_t cmp_func) {
if (element_count < PAR_QSORT_LOW_LIMIT) {
qsort(data, element_count, element_size, cmp_func);
} else {
void* pivot = partition(data, element_count, element_size, cmp_func);
size_t left = (pivot - data) / element_size;
size_t right = element_count - left - 1;
#pragma omp parallel sections firstprivate(data, element_count, element_size, cmp_func, pivot, left, right)
{
#pragma omp section
par_qsort(data, left, element_size, cmp_func);
#pragma omp section
par_qsort(pivot + element_size, right, element_size, cmp_func);
}
}
}
#endif
typedef struct {
double error;
double* weights;
} individual_t;
int actual_weight_count(void) {
int k = 0;
int i, j;
for (i = 0; i < NNUM; ++i) {
for (j = 0; j < NEURON[i].nw; ++j) {
k++;
}
}
return k;
}
void crossover(double w1, double w2, double* n1, double* n2) {
static int POSS = 1;
double average = (w1 + w2) / 2;
double mid = (WMAX + WMIN) / 2;
*n1 = average;
if (average > mid) {
*n2 = average - (WMAX - WMIN) / 2;
} else if (average < mid) {
*n2 = average + (WMAX - WMIN) / 2;
} else { /* == mid*/
if (POSS) {
*n2 = WMAX;
POSS = 0;
} else {
*n2 = WMIN;
POSS = 1;
}
}
}
void mutation(double* weight, double rate) {
if (rnd() > rate) return;
if (rnd() > 0.5) {
/* go plus */
*weight += (rnd() * (WMAX - WMIN) / 2);
if (*weight > WMAX) *weight -= (WMAX - WMIN);
} else {
/* go minus */
*weight -= (rnd() * (WMAX - WMIN) / 2);
if (*weight < WMIN) *weight += (WMAX - WMIN);
}
}
int individual_compare(const void* a, const void* b) {
individual_t* ia = *(individual_t**)a;
individual_t* ib = *(individual_t**)b;
if (ia->error > ib->error) return 1;
if (ia->error < ib->error) return -1;
return 0;
}
void init_individuals(individual_t** individuals, int size) {
int n;
#ifdef _OPENMP
#pragma omp parallel for shared(NNUM, NEURON, individuals) private(n)
#endif
for (n = 0; n < size; ++n) {
int k = 0;
int i, j;
for (i = 0; i < NNUM; i++) {
for (j = 0; j < NEURON[i].nw; j++) {
individuals[n]->weights[k] = rnd();
k++;
}
}
}
}
void reproduce_next_generation(individual_t** individuals, int size, int weight_cout, double rate) {
int pos = size;
int i, j, k;
#ifdef _OPENMP
#pragma omp parallel for shared(pos, individuals, size, weight_cout, rate) private(i, j, k)
#endif
for (i = 0; i < size; ++i) {
for (j = i + 1; j < size; ++j) {
for (k = 0; k < weight_cout; ++k) {
double w1 = individuals[i]->weights[k];
double w2 = individuals[j]->weights[k];
crossover(w1, w2, individuals[pos]->weights + k, individuals[pos + 1]->weights + k);
mutation(individuals[pos]->weights + k, rate);
mutation(individuals[pos + 1]->weights + k, rate);
}
#ifdef _OPENMP
#pragma omp atomic
#endif
pos += 2;
}
}
}
void selection(individual_t** individuals, int size) {
int pool_size = size * size;
int n;
#ifdef _OPENMP
// issue here because network and neuron are both global here, and even use
// firstprivate cannot make local copy correctly. without parallelized error
// calculation, everything goes correct.
// in future, we might need a clone_network method to make a local copy of
// network OR make a Neuron ownership belongs to network
#pragma omp parallel for shared(individuals, NEURON, NETWORK) private(n)
//#pragma omp parallel for shared(individuals) private(n) firstprivate(NEURON, NETWORK)
#endif
for (n = 0; n < pool_size; ++n) {
#pragma omp critical
{
int k = 0;
int i, j;
for (i = 0; i < NNUM; i++) {
for (j = 0; j < NEURON[i].nw; j++) {
NEURON[i].w[j] = individuals[n]->weights[k];
k++;
}
}
individuals[n]->error = error(ERROR_TYPE);
}
}
#ifdef _OPENMP
par_qsort(individuals, pool_size, sizeof(individual_t*), individual_compare);
#else
qsort(individuals, pool_size, sizeof(individual_t*), individual_compare);
#endif
}
void genetic_algorithm(int output /* screen output - on/off */,
int nmax /* number of generations */,
int npop /* number of individuals per generation */,
double rate /* rate of change between one generation and the parent */,
double eps /* numerical accuracy */) {
int i, j, k, n;
int pool_size = npop * npop;
individual_t** individuals = malloc(pool_size * sizeof(individual_t*));
if (individuals == NULL) {
printf("GA: Not enough memory to allocate individual index table\n");
exit(0);
}
for (i = 0; i < pool_size; ++i) {
individuals[i] = malloc(sizeof(individual_t));
if (individuals[i] == NULL) {
printf("GA: Not enough memory to allocate individuals\n");
exit(0);
}
individuals[i]->weights = malloc(NNUM * MAX_IN * sizeof(double));
if (individuals[i]->weights == NULL) {
printf("GA: Not enough memory to allocate weights\n");
exit(0);
}
}
init_individuals(individuals, npop);
int weight_cout = actual_weight_count();
for (n = 0; n < nmax; ++n) {
reproduce_next_generation(individuals, npop, weight_cout, rate);
selection(individuals, npop);
if (output == ON) printf("GA2: %d %.12g\n", n, individuals[0]->error);
if (individuals[0]->error < eps) break;
}
if (individuals[0]->error > eps) {
if (output == ON) printf("GA2: error still greater than %g after %d iterations", eps, nmax);
}
k = 0;
for (i = 0; i < NNUM; i++) {
for (j = 0; j < NEURON[i].nw; j++) {
NEURON[i].w[j] = individuals[0]->weights[k];
k++;
}
}
for (i = 0; i < pool_size; ++i) {
free(individuals[i]->weights);
free(individuals[i]);
}
free(individuals);
}