/* NAME
* hopfield - solve a task assignment problem via a Hopfield network
* NOTES
* Since the weight matrix is exceptionally sparse for this
* network, we don't actually store it but, instead, compute it
* on the fly by checking if two neurons are in the same row
* or column. Moreover, all information about the neurons is
* actually stored in one-dimensional structures, since using
* two-dimensional structures doesn't really simplify anything
* in this case.
* MISCELLANY
* As mentioned before, the weights and external inputs are set
* according to the K-out-of-N rule which states that if we have N
* neurons in a mutually connected sub-network and that we wish this
* subset to converge with exactly K neurons activated, then each
* neuron should be connected to every other neuron with a weights
* of -2 and receive an external input of (2K - 1). Since we must
* produce solutions that look like permutation matrices, each
* neuron is in 2 K-out-of-N subsets, one for the column and one for
* the row. Thus, all neurons inhibit all other neurons in the same
* column or row with -2 and must (on average) receive a net input
* of 2 since they are all in two sets. The external inputs are
* slightly adjusted to favor neurons that represent more productive
* task performers. See the source code for more details.
* BUGS
* No sanity checks are performed to make sure that any of the
* options make sense.
*
* The final cost of the solution is printed at the end of the
* simulation; however, no check is done to insure that the system
* has actually converged. Hence, it may print out nonsense
* results.
* AUTHOR
* Copyright (c) 1997, Gary William Flake.
*
* Permission granted for any use according to the standard GNU
* ``copyleft'' agreement provided that the author's comments are
* neither modified nor removed. No warranty is given or implied.
*/
#include
#include
#include
#include "misc.h"
char help_string[] = "\
Solve a task assignment problem via a Hopfield neural network while \
plotting the activations of the neurons over time. The program uses \
the K-out-of-N rule for setting the external inputs and synapse \
strength of the neurons. \
";
double dt = 0.1, tau = 10.0, scale = 0.5, gain = 0.5;
int seed = 0, steps = 1000, invert = 0, mag = 10, gray = 256;
char *term = NULL, *specs = "data/hop1.dat";
OPTION options[] = {
{ "-specs", OPT_STRING, &specs, "Problem specification file." },
{ "-dt", OPT_DOUBLE, &dt, "Time step increment." },
{ "-tau", OPT_DOUBLE, &tau, "Decay term." },
{ "-gain", OPT_DOUBLE, &gain, "Sigmoidal gain." },
{ "-scale", OPT_DOUBLE, &scale, "Scaling for inputs." },
{ "-seed", OPT_INT, &seed, "Random seed for initial state." },
{ "-steps", OPT_INT, &steps, "Number of time steps." },
{ "-gray", OPT_INT, &gray, "Number of gray levels." },
{ "-inv", OPT_SWITCH, &invert, "Invert all colors?" },
{ "-mag", OPT_INT, &mag, "Magnification factor." },
{ "-term", OPT_STRING, &term, "How to plot points." },
{ NULL, OPT_NULL, NULL, NULL }
};
double *U, *UU, *V, *I, *cost;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Compute a sigmid function bounded by 0 and 1. */
double sigmoid(double x)
{
return(1.0 / (1.0 + exp(-x * gain)));
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Read in the specifications file which contains a single integer, n,
that specifies the width and height and n*n numbers representing
the costs for specific performers to do specific tasks. */
void read_specs(char *fname, int *width, int *height)
{
double min = 10e10, max = -10e10, ave = 0.0;
FILE *fp;
SCANNER *scan;
char *str;
int i;
if(fname == NULL || (fp = fopen(fname, "r")) == NULL) {
fprintf(stderr, "Cannot open specification file \"%s\".\n", fname);
exit(1);
}
/* Let '#' be a comment character. */
scan = scan_init(fp, "", " \t\n", "#");
/* Get the width (height) of the grid to work in. */
if((str = scan_get(scan)) == NULL) goto BADFILE;
*height = *width = atoi(str);
/* Allocate space for the state, next state, activation,
* external inputs, and and costs.
*/
U = xmalloc(sizeof(double) * *width * *height);
UU = xmalloc(sizeof(double) * *width * *height);
V = xmalloc(sizeof(double) * *width * *height);
I = xmalloc(sizeof(double) * *width * *height);
cost = xmalloc(sizeof(double) * *width * *height);
for(i = 0; i < *width * *height; i++) {
if((str = scan_get(scan)) == NULL) goto BADFILE;
/* Read in the costs. */
cost[i] = I[i] = atof(str);
/* Set the state to something random. */
U[i] = random_range(-1, 1);
/* Pass the state through a sigmoid to get the state. */
V[i] = sigmoid(U[i]);
/* Keep track of the minimum, maximum, and average cost
* so that we rescale things.
*/
if(I[i] < min) min = I[i];
if(I[i] > max) max = I[i];
ave += I[i];
}
ave /= (*width * *height);
/* Rescale so that the mean is 2 (per the k-out-of-n rule). */
for(i = 0; i < *width * *height; i++)
I[i] = scale * (I[i] - ave) / (max - min) + 2;
return;
BADFILE:
fprintf(stderr, "Problem found in specification file.\n");
exit(1);
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
int main(int argc, char **argv)
{
extern int plot_mag;
extern int plot_inverse;
int width, height, i, j, k, l, m, o, t, n;
double *swap, sum;
get_options(argc, argv, options, help_string);
srandom(seed);
read_specs(specs, &width, &height);
plot_mag = mag;
plot_inverse = invert;
plot_init(width, height, gray, term);
plot_set_all(0);
n = width * height;
/* For every time step... */
for(t = 0; t < steps; t++) {
/* Use k as an index into U and V. */
k = 0;
/* For every neuron... */
for(i = 0; i < height; i++)
for(j = 0; j < width; j++) {
/* Compute the activation an plot the point. */
V[k] = sigmoid(U[k]);
plot_point(j, i, (int) (V[k] * gray));
k++;
}
/* Use k as an index into U and V. */
k = 0;
/* For every neuron... */
for(i = 0; i < height; i++)
for(j = 0; j < width; j++) {
o = 0;
sum = 0.0;
/* For every other neuron... */
for(l = 0; l < height; l++)
for(m = 0; m < width; m++) {
/* If neuron(l, m) is in the same row or column as
* neuron(i, j), then it must inhibit the former
* neuron with a weight of -2 (per the k-out-of-n rule).
*/
if((i == l && j != m) || (i != l && j == m))
sum += -2.0 * V[o];
o++;
}
/* Update the next state. */
UU[k] = U[k] + dt * (sum + I[k] - U[k] / tau);
k++;
}
/* Let the next state be the current state. */
swap = U; U = UU; UU = swap;
}
/* We are done, so calculate the final cost. Note that this
* fails if the system isn't close to converging.
*/
sum = 0.0;
for(i = 0; i < n; i++)
if(V[i] > 0.5) sum += cost[i];
fprintf(stderr, "Final cost = %f\n", sum);
plot_finish();
exit(0);
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */