blob: 536e6e57b61e0455e27357055f4411ad4cfe09e5 [file] [log] [blame]
/* (C) 1996, 2001 Fredrik Warg
*
* CHANGELOG
* 2001-02-06: This program was originally an assignment in a course in
* neural networks, all the way back in 1996. It has been modified for use
* with FreeBench.
*
* 1996-09-29:
* This program stores letters represented as x*y bit patterns in a Hopfield
* network using Hebbian/Delta learning. The program can be trained with all letters
* at once or step-by-step so that the progress can be studied closely. Functions
* for unlearning using random vectors and for creating generators for the
* stored patterns using the clamping method are also available. For the function
* that find the closest stable state for a probe vector, both bipolar and
* continuous versions are available. The program uses a simple menu system for
* manouvering. The letters are read from the file 'w.txt'.
*
* The program has no error-detection code. Erroneus input or failure to find
* the input file will create undefined output. So will using the functions
* in the menu in the wrong order or using a wrongly formatted input file :)
*/
/* include files */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#define BENCHMARK /* Run the program as a benchmark */
/* flags for normal or complement hamming distance computation mode */
#define MODE_NORMAL 1
#define MODE_COMPLEMENT -1
/* flags for all at once or step by step T-matrix computation mode */
#define MODE_ALL 1
#define MODE_TRACE 2
/* flags for continuous or two-valued neuron mode */
#define MODE_CONT 1
#define MODE_BIN 2
/* Parametrizing the NN */
/*
#define NNWIDTH 30
#define NNHEIGHT 42
#define NNTOT 1260
#define NUMPATS 10
*/
int NNWIDTH;
int NNHEIGHT;
int NNTOT;
int NUMPATS;
/* Typedefs */
typedef enum {WFALSE, WTRUE} wbool;
typedef float real;
/* Global variables */
/*
char vnames[NUMPATS];
int vectors[NUMPATS][NNTOT], newvectors[NUMPATS][NNTOT], generators[NUMPATS][NNTOT];
real Tmatrix[NNTOT][NNTOT];
*/
char *vnames;
int *stored;
real **Tmatrix;
int **vectors, **newvectors, **generators;
int nmode=MODE_BIN;
unsigned long randnum;
/* Function Declarations */
static int hamming(int *ny, int *orig, int mode);
static void checkham ();
static void generateT (int mode);
static void delta(real n);
static int run (signed int *source, signed int *dest);
static void readvector (FILE *fp);
static void storecheck ();
static void printT();
static void printV(int vector, signed int *vect);
static void unlearn(int seed, int iter);
static int runcont(signed int *source, signed int *dest);
static void mysrand(unsigned long seed);
static real myrand();
static double myexp(double in);
#ifndef BENCHMARK
static void printvec (int vecs[][]);
static void makegenerators(int n);
static void usegenerators(int n);
#endif
/* The main function types the menu and calls the appropriate function requested by the user
* until the user types 'q' to quit the program. */
int main (int c, char *v[])
{
FILE *fp;
char indata[100];
int i;
fprintf(stderr,"Compile date: %s\n", COMPDATE);
fprintf(stderr,"Compiler switches: %s\n", CFLAGS);
if (c!=2) {
fprintf(stderr,"Wrong number of arguments, 1 needed, %d specified.\n",c-1);
fprintf(stderr,"USAGE: %s <datafile>\n",v[0]);
exit(1);
}
fp=fopen(v[1], "r");
if (fp==NULL) {
fprintf(stderr,"ABORT: Could not read datafile %s\n",v[1]);
exit(1);
}
/* Size of NeuralNet specifed in datafile. */
fgets(indata,99,fp);
NNWIDTH = atoi(indata);
fgets(indata,99,fp);
NNHEIGHT = atoi(indata);
fgets(indata,99,fp);
NUMPATS = atoi(indata);
NNTOT = NNWIDTH*NNHEIGHT;
printf("Matrix size is %dx%d\n",NNWIDTH,NNHEIGHT);
/* Allocating lots of space... */
vnames = (char *)malloc(sizeof(char)*NUMPATS);
stored = (int *)malloc(sizeof(int)*NUMPATS);
if (!vnames || !stored) {
fprintf(stderr,"ABORT: Out of memory\n");
exit(1);
}
Tmatrix = (real **)malloc(sizeof(real *)*NNTOT);
if (!Tmatrix) {
fprintf(stderr,"ABORT: Out of memory\n");
exit(1);
}
for (i=0; i<NNTOT; i++) {
Tmatrix[i] = (real *)malloc(sizeof(real)*NNTOT);
if (!Tmatrix[i]) {
fprintf(stderr,"ABORT: Out of memory\n");
exit(1);
}
}
vectors = (int **)malloc(sizeof(int *)*NUMPATS);
newvectors = (int **)malloc(sizeof(int *)*NUMPATS);
generators = (int **)malloc(sizeof(int *)*NUMPATS);
if (!vectors || !newvectors || !generators) {
fprintf(stderr,"ABORT: Out of memory\n");
exit(1);
}
for (i=0; i<NUMPATS; i++) {
vectors[i] = (int *)malloc(sizeof(int)*NNTOT);
newvectors[i] = (int *)malloc(sizeof(int)*NNTOT);
generators[i] = (int *)malloc(sizeof(int)*NNTOT);
if (!vectors[i] || !newvectors[i] || !generators[i]) {
fprintf(stderr,"ABORT: Out of memory\n");
exit(1);
}
}
/* Do some operations (This is for FreeBench) */
readvector(fp); /* read from file */
printf("Checking hamming distances...\n");
checkham(); /* check hamming distances */
printf("Generating T matrix...\n");
generateT(MODE_ALL); /* generate t matrix */
#if 0
printf("Store check...\n");
storecheck(); /* check if vectors were stored */
printf("Applying unlearning vectors...\n");
unlearn(10,15);
storecheck(); /* check if vectors were stored */
#endif
nmode=MODE_CONT; /* set continuous neuron mode */
printf("Delta learning...\n");
delta(0.5); /* try to learn vectors using delta learning */
printf("Store check...\n");
storecheck(); /* check if vectors were stored */
/* original menu system removed
char option;
printf("\nNeural Computing - assignment 1\n\n");
do {
printf("\nr - read vectors from file and check hamming distances\np - print original vectors");
printf("\nt - generate T-matrix\ns - generate T-matrix step-by-step");
printf("\nc - check if vectors are stored\nu - unlearning");
printf("\ng - compute generators for stored vectors and use the generators to print original vectors");
printf("\nm - print T-matrix\nz - set continuous neuron mode\nx - set two-valued neuron mode (default)");
printf("\nq - quit program\nOption > ");
option=getchar();
getchar();
switch(option) {
case 'r' :
readvector();
checkham();
break;
case 'p' :
printvec(vectors);
break;
case 't' :
generateT(MODE_ALL);
break;
case 's' :
generateT(MODE_TRACE);
break;
case 'c' :
storecheck();
break;
case 'u' :
unlearn();
break;
case 'g' :
makegenerators();
usegenerators();
break;
case 'm' :
printT();
break;
case 'z' :
nmode=MODE_CONT;
printf("Continuous meurons used!");
break;
case 'x' :
nmode=MODE_BIN;
printf("Two-valued neurons used");
break;
}
} while(option != 'q');
*/
return 0;
} /* main */
/* Computes the hamming distance between ny and orig, two 35-bit vectors.
* Mode is one of MODE_NORMAL or MODE_COMPLEMENT. The latter computes the
* hamming distence between ny and the complement vector of orig. */
static int hamming(signed int *ny, signed int *orig, int mode)
{
int hd=0, neuron;
for(neuron=0; neuron<NNTOT; neuron++)
if(ny[neuron] != (orig[neuron]*mode))
hd++;
return hd;
} /* hamming */
/* Checks the hamming distance between all vectors and complements of the global
* ten-letter storage variable 'vectors'. If the hamming distance
* is less than 2 the output is highlighted with square brackets because a hamming
* distance of <2 is not allowed and the original vectors will have to be changed. */
static void checkham()
{
int vec, comp, hd;
int hamwarn=0;
/* printf("\nHamming distances for original vectors:\n"); */
for(vec=0; vec<NUMPATS; vec++) {
for(comp=(vec+1); comp<NUMPATS; comp++) {
if((hd=hamming(vectors[vec], vectors[comp], MODE_NORMAL))<2)
/* printf ("[!!%c-%c->%d!!] ", vnames[vec], vnames[comp], hd); */ hamwarn++;
else
/* printf ("%c-%c->%d ",vnames[vec], vnames[comp], hd) */ ;
if((hd=hamming(vectors[vec], vectors[comp], MODE_COMPLEMENT))<2)
/* printf ("[!!%c-%c'->%d!!] ", vnames[vec], vnames[comp], hd); */ hamwarn++;
else
/* printf ("%c-%c'->%d ",vnames[vec], vnames[comp], hd)*/ ;
} /* for */
} /* for */
/* printf("\n"); */
if (hamwarn)
printf("WARNING: %d vectors have a hamming distance <2, please modify input vectors!\n",hamwarn);
} /* checkham */
/* Generates a T-matrix with Hebbian learning. Mode can be MODE_ALL or MODE_TRACE.
* MODE_ALL trains all 10 vectors at once and MODE_TRACE views the current
* learning status between each vector. */
static void generateT(int mode)
{
int row, col, vec;
char option='0';
for(row=0; row<NNTOT; row++)
for(col=0; col<NNTOT; col++)
Tmatrix[row][col]=0.0;
for(vec=0; vec<10; vec++) {
for(row=0; row<NNTOT; row++) {
for(col=0; col<NNTOT; col++) {
if(row==col)
Tmatrix[row][col]=0.0;
else
Tmatrix[row][col]+=vectors[vec][row]*vectors[vec][col];
} /* for */
} /* for */
if (mode==MODE_TRACE) {
storecheck();
printf("\nc-cont, b-break: ");
option=getchar();
getchar();
} /* if */
if(option=='b')
break;
} /* for */
} /* generateT */
/* Trains the T-matrix with Delta learning. The learning rate, c, can be
* altered in order to study the effects of different learning rates. */
static void delta (real n)
{
int vec, row, col, neuron;
real *tempvecC; /* n=0.5 */
wbool status;
tempvecC=(real *)malloc(NNTOT*sizeof(real));
if (!tempvecC) {
fprintf(stderr,"ABORT: Out of memory\n");
exit(1);
}
/* Set learning rate
if (nn==-1.0) {
printf("\nChoose value n: ");
scanf("%f", &n);
getchar();
*/
do {
status=WTRUE;
for(vec=0; vec<NUMPATS; ++vec) {
if(nmode==MODE_BIN)
run(vectors[vec], newvectors[vec]);
else
runcont(vectors[vec], newvectors[vec]);
for(neuron=0; neuron<NNTOT; ++neuron)
if((tempvecC[neuron]=(real)(vectors[vec][neuron]-newvectors[vec][neuron])*n) != 0.0)
status=WFALSE;
for(row=0; row<NNTOT; ++row) {
for(col=0; col<NNTOT; ++col) {
if(row==col)
Tmatrix[row][col]=0.0;
else
Tmatrix[row][col]+=tempvecC[row]*(real)(vectors[vec][col]);
} /* for */
} /* for */
} /* for */
} while (!status);
} /* delta */
/* Updates the vector in source with asynchronous updating and places the result in dest.
* This function uses the sgn function for the updating. The number of iterations needed
* to reach a stable state is returned. */
static int run (signed int *source, signed int *dest)
{
signed int neuron, thesum, row, max=0, *tempvecA, *tempvecB;
wbool stable=WFALSE;
tempvecA=(int *)malloc(NNTOT*sizeof(int));
tempvecB=(int *)malloc(NNTOT*sizeof(int));
if (!tempvecA || !tempvecB) {
fprintf(stderr,"ABORT: Out of memory\n");
exit(1);
}
for(neuron=0; neuron<NNTOT; neuron++)
tempvecA[neuron]=source[neuron];
while((!stable) && (max<500)) {
for(row=0; row<NNTOT; row++) {
thesum=0;
for(neuron=0; neuron<NNTOT; neuron++)
thesum+=Tmatrix[row][neuron]*tempvecA[neuron];
tempvecB[row]= (thesum>=0) ? 1 : -1;
} /* for */
for(neuron=0; neuron<NNTOT; neuron++)
if(tempvecA[neuron]==0)
tempvecA[neuron]=tempvecB[neuron];
if(hamming(tempvecB, tempvecA, MODE_NORMAL)==0)
stable=WTRUE;
else {
neuron=0;
while((neuron<NNTOT) && (tempvecB[neuron]==tempvecA[neuron]))
neuron++;
tempvecA[neuron]=tempvecB[neuron];
} /* else */
max++;
} /* while */
if(max==500)
printf("Warning! No stable state reached after 500 iterations, aborting!");
for(neuron=0; neuron<NNTOT; neuron++)
dest[neuron]=tempvecA[neuron];
return max;
} /* run */
/* Reads NUMPATS bit patterns from the file 'w.txt' and places them in 'vectors'. The actual
* letters are stored in 'vnames'. The patterns are stored as -1 (.) and +1 (X). */
static void readvector (FILE *fp)
{
char *srow;
int vec, row, column, vpos;
srow = (char *)malloc((NNWIDTH+2)*sizeof(char));
if (!srow) {
fprintf(stderr,"ABORT: Out of memory\n");
exit(1);
}
for(vec=0; vec<NUMPATS; vec++) {
vpos=0;
fscanf(fp, "%s", srow);
vnames[vec]=srow[0];
for(row=0; row<NNHEIGHT; row++) {
fscanf(fp, "%s", srow);
for(column=0; column<NNWIDTH; column++) {
vectors[vec][vpos] = (srow[column]=='X') ? 1 : -1;
vpos++;
} /* for */
} /* for */
} /* for */
fclose(fp);
printf("Vectors read from file!\n");
} /* readvector */
/* Checks all original vectors to see if they are stored. All vectors are written on the
* screen. If a vector is stored the word 'Stored' appears beneath the typed pattern,
* otherwise the hamming distance between the original and computed vector is displayed.
* The number of iterations needed to reach a stable state is displayed below the "hamming
* distance or stored" line. */
static void storecheck ()
{
int vec, hd, *iter;
iter = (int *)malloc((NUMPATS)*sizeof(int));
if (!iter) {
fprintf(stderr,"ABORT: Out of memory\n");
exit(1);
}
for(vec=0; vec<NUMPATS; vec++)
if(nmode==MODE_BIN)
iter[vec]=run(vectors[vec], newvectors[vec]);
else
iter[vec]=runcont(vectors[vec], newvectors[vec]);
/* putchar('\n');
printvec(newvectors); */
for(vec=0; vec<NUMPATS; vec++) {
if((hd=hamming(vectors[vec], newvectors[vec], MODE_NORMAL))==0) {
stored[vec]=1;
printf("Pattern %d stored.\n", vec);
}
else {
stored[vec]=0;
printf("Pattern %d: hamming distance=%-.2d.\n",vec,hd);
}
} /* for */
/*
printf("\nNumber of iterations used:\n");
for (vec=0; vec<NUMPATS; vec++)
printf("%.5d ", iter[vec]);
putchar('\n');
*/
} /* storecheck */
#ifndef BENCHMARK
/* Prints the vectors in the NUMPATS vector storage variable 'vecs' on the screen. -1's are
* printed as '.' and +1's as 'X' */
static void printvec (int vecs[][])
{
int row, vec, neuron;
for(row=0; row<NNHEIGHT; row++) {
for(vec=0; vec<NUMPATS; vec++) {
for(neuron=0; neuron<NNWIDTH; neuron++) {
if(vecs[vec][row*NNWIDTH+neuron]==1)
putchar('X');
else
putchar('.');
} /* for */
printf(" ");
} /* for */
putchar('\n');
} /* for */
putchar ('\n');
} /* printvec */
#endif
/* Prints the T-matrix on the screen. */
static void printT()
{
int row, col;
putchar('\n');
printf("The T-matrix:\n");
for (row=0; row<NNTOT; row++) {
for(col=0; col<NNTOT; col++)
printf("%.1f ", Tmatrix[row][col]);
putchar('\n');
} /* for */
putchar('\n');
} /* printT */
/* Prints the vector vect on screen as 1's and -1's. The argument 'vector' is used to
* indicate the number of the vector so that the actual letter can be printed from vnames. */
static void printV (int vector, signed int vect[])
{
int neuron;
printf("%c -> ",vnames[vector]);
for (neuron=0; neuron<NNTOT; ++neuron)
if(vect[neuron] != 0)
printf("%d ", vect[neuron]);
putchar('\n');
} /* printV */
/* Applies random unlearning to the T-matrix. The user is asked to type in a random
* seed and number of unlearning vectors (recommended about 100).
* Unlearning could improve the number of stored patterns, on the other hand, it
* might also make things worse ;)
*/
static void unlearn (int seed, int iter)
{
int vecs, neuron, row, col;
real *tempvec;
tempvec = (real *)malloc((NNTOT)*sizeof(real));
if (!tempvec) {
fprintf(stderr,"ABORT: Out of memory\n");
exit(1);
}
/*
printf("\nRandom seed: ");
scanf("%d", &seed);
srand(seed);
printf("\nNumber of unlearning vectors: ");
scanf("%d", &iter);
getchar();
*/
mysrand(seed);
for(vecs=0; vecs<iter; vecs++) {
for(neuron=0; neuron<NNTOT; neuron++)
tempvec[neuron]= (myrand()>=0.5) ? 1 : -1;
for(row=0; row<NNTOT; row++) {
for(col=0; col<NNTOT; col++) {
if(row==col)
Tmatrix[row][col]=0.0;
else
Tmatrix[row][col]+=0.01*tempvec[row]*tempvec[col];
} /* for */
} /* for */
} /* for */
} /* unlearn */
/* Creates generators for all (or n first) stored vectors in 'newvectors' and places the
* generators in the global variable 'generators'. The generators are found using
* the clamping method. */
#ifndef BENCHMARK
static void makegenerators (int n)
{
int vec, neuron, generator, count=0;
signed int probe[NNTOT], dest[NNTOT];
probe = (int *)malloc((NNTOT)*sizeof(int));
dest = (int *)malloc((NNTOT)*sizeof(int));
if (!probe || !dest) {
fprintf(stderr,"ABORT: Out of memory\n");
exit(1);
}
for(vec=0; vec<NUMPATS; vec++)
for(neuron=0; neuron<NNTOT; neuron++)
generators[vec][neuron]=0;
for(vec=0; vec<NUMPATS; vec++) {
if(hamming(vectors[vec], newvectors[vec], MODE_NORMAL)==0) {
generator=0;
for(neuron=0; neuron<NNTOT; neuron++)
probe[neuron]=0;
do {
probe[generator]=newvectors[vec][generator];
if(nmode==MODE_BIN)
run(probe, dest);
else
runcont(probe, dest);
generator++;
} while((hamming(dest, newvectors[vec], MODE_NORMAL) != 0) && (generator<NNTOT));
for(neuron=0; neuron<NNTOT; neuron++)
generators[vec][neuron]=probe[neuron];
count++; if (count>=n) break;
} /* if */
} /* for */
} /* generators */
/* Proves that the generators works by computing the original vector from the
* generator and print it on the screen. */
static void usegenerators (int n)
{
signed int genvec[NUMPATS][NNTOT];
int vec, neuron, count=0;
for(vec=0; vec<NUMPATS; vec++)
for(neuron=0; neuron<NNTOT; neuron++)
genvec[vec][neuron]=0;
for(vec=0; vec<NUMPATS; vec++) {
if(generators[vec][0] != 0) {
printV(vec, generators[vec]);
if(nmode==MODE_BIN)
run(generators[vec], genvec[vec]);
else
runcont(generators[vec], genvec[vec]);
} /* if */
if (stored[vec]) count++; if (count>=n) break;
}
printf("\nThe vectors recreated using the generators:\n");
printvec(genvec);
putchar('\n');
} /* usegenerators */
#endif
/* Same as run, updates 'source' until a stable state is reached and copies the
* computed vector to 'dest'. But runcont uses continuous neurons. Number of iterations
* needed to reach a stable state is returned. */
static int runcont (signed int source[], signed int dest[])
{
signed int neuron, row, max=0, maxcont=0;
real *tempvecA, thesum;
signed int *tempvecC;
wbool stable=WFALSE, threshold=WFALSE;
tempvecA = (real *)malloc((NNTOT)*sizeof(real));
tempvecC = (signed int *)malloc((NNTOT)*sizeof(signed int));
if (!tempvecA || !tempvecC) {
fprintf(stderr,"ABORT: Out of memory\n");
exit(1);
}
for(neuron=0; neuron<NNTOT; neuron++)
tempvecA[neuron]=dest[neuron]=source[neuron];
while((!stable) && (max<500)) {
maxcont=0;
for(row=0; row<NNTOT; row++) {
thesum=0.0;
for(neuron=0; neuron<NNTOT; neuron++)
thesum+=Tmatrix[row][neuron]*source[neuron];
tempvecA[row]=(1.0-myexp(-1.0*thesum))/(1.0+myexp(-1.0*thesum));
} /* for */
while((!threshold) && (maxcont<50)) {
threshold=WTRUE;
for(row=0; row<NNTOT; row++) {
if(fabs(tempvecA[row])<0.7) {
thesum=0.0;
for(neuron=0; neuron<NNTOT; neuron++)
thesum+=Tmatrix[row][neuron]*tempvecA[neuron];
if ((tempvecA[row]=(1.0-myexp(-1.0*thesum))/(1.0+myexp(-1.0*thesum)))<0.7)
threshold=WFALSE;
} /* if */
}
maxcont++;
} /* while */
for(neuron=0; neuron<NNTOT; neuron++)
tempvecC[neuron] = (tempvecA[neuron]>0) ? 1 : -1;
if(hamming(dest, tempvecC, MODE_NORMAL)==0)
stable=WTRUE;
else {
neuron=0;
while((neuron<NNTOT) && (dest[neuron]==tempvecC[neuron]))
neuron++;
dest[neuron]=tempvecC[neuron];
for(neuron=0; neuron<NNTOT; neuron++)
tempvecA[neuron]=dest[neuron];
} /* else */
max++;
} /* while */
if(max==500)
printf("Warning! No stable state reached after 500 iterations!");
return max;
} /* runcont */
/* A pseudo-random algorithm, just to make sure you always get the
* same pseudo-random sequence, to make sure the benchmark is fair for all
* platforms. For our purposes, it is not very important that this algorithm
* gives a good random sequence, so don't worry about the quality of it.
*/
static void mysrand(unsigned long seed) {
randnum = seed;
}
static real myrand() {
randnum = (randnum*1103515245+12345)%4294967296;
return ((real)randnum/(real)4294967296);
}
static double myexp(double in)
{
if (in>2.0e2)
in=200.0;
if (in<-2.0e2)
in=-200.0;
return exp(in);
}
/* END OF PROGRAM */