Project 2 — Hopfield Net

Data structures

You will need a structure to hold the patterns (each with 100 neurons), a 50x100 array will work. A 100x100 array can be used to store the weights of the network. You will want to represent the neural network itself, such as with a 100 element array. You will also want data structures to keep track of the number of stable imprints and probability of stable imprints, and possibly similar structures for storing the averages over multiple runs. Arrays of 50 elements can work for this. For the 527 part, you will want to keep track of the number and frequencies of the basin sizes with something like a 50x50 array.

Basic code structure

The overall logical flow for main() should be something like this:

initialize data structures for keeping statistics
for i from 0 to number of runs
  generatePatterns()
  for p from 1 to 50
    imprintPatterns(p)
    testPatterns(p)
compute averages over number of runs
normalize data for basins of attraction (527 students)
write data file

generatePatterns()

Randomly initialize 50 vectors, each with 100 elements, where each element is 1 or -1. Be sure to seed the random generator in main() with srand(time(NULL)); or equivalent in your chosen programming language.

imprintPatterns(p)

This is where you compute the various weights associated with the neurons, which you will use to calculate the net input (local field) in a later step. The formula for computing the weights is given by:



This will calculate the weight associated with neurons i and j, where i and j each range from 1 to 100 (or 0 to 99, depending on your array indexing scheme). So each weight represents a pair of neurons. This implies you will have a 100x100 array of weights, as mentioned above.

Start by iterating through i and j. Then if i and j are not equal (i.e., you have two different neurons), do the following. Have index k loop through each of the patterns 1 through p (the parameter passed in). The si and sj in the above formula represent the state values (1 or -1) for neurons i and j, respectively, for pattern k. So you will take the product of the state values of neurons i and j for pattern k, and accumulate the sum of these products over the p imprinted patterns (in the k loop). After accumulating the sum, divide this sum by the number of neurons N (100 in our case). If i and j are equal, then assign the corresponding weight to 0. This is what is meant by no self-coupling, a neuron does not have a weight associated with itself.

So the first time imprintPatterns(p) is called from main(), it will imprint pattern 1. The next time it is called, it will imprint patterns 1 and 2. The third time it is called, it will imprint patterns 1, 2, and 3, and so on. This is how the inner k loop iterates through the p patterns.

testPatterns(p)

Here is where you will determine the number and fraction of imprints that are stable (or unstable). First, iterate k from 1 to p (the parameter passed in), and inside that for loop, do the following:

a. Set the neural net (100 element array) to the current pattern k by simply copying the array elements over.

b. For each of the 100 neurons (for loop and formula index i) in the neural net that you just set in the previous step, first compute its new state value using the following formulas:



The first formula computes the local field of the neuron. The variable j iterates through each neuron of the neural net (100 element array), and multiplies the neuron's state value with its associated weight matrix value. The variable i represents the current neuron being considered at the beginning of step b), so we are interested in row i of the weight matrix. Keep accumulating this sum of products over the neural net to compute the local field of the neuron of interest.

The other two formulas determine the next state of the current neuron, -1 if its local field h is negative, and +1 if its local field h is nonnegative.

After getting the neuron's new state, compare it to the neuron's current state (the corresponding value you assigned to the neural net in step a).

If any of the 100 elements of the neural net differ from its corresponding new state value, then that imprinted pattern that was assigned to the neural net is NOT stable. Otherwise, if each element matches its new state based on the local field computation, then that imprinted pattern IS stable. You will probably want a boolean to keep track of whether or not the current imprinted pattern is stable.

Note: all of the items in this step b) are contained in a loop that iterates through the 100 elements of the neural net.

c. If the pattern is stable, increment a counter indicating the number of stable patterns for the current p. You can use an array of 50 elements for this (one for each p).

d. To compute the probability of stable imprints for each p, divide the number of stable imprints for that p by that number p. To get the probability of UNstable imprints for that p, subtract the probability of stable imprints for that p from 1. Note that probabilities should never be greater than 1. Also, the number of stable (or unstable) imprints for any value of p should never be greater than p itself.

Graphing the data

You should generate the following two graphs:

a. The fraction of unstable imprints as a function of the number of imprints (p=1 to 50)



b. The number of stable imprints as a function of the number of imprints (p=1 to 50)



You can just write the data to a .csv file, and then use a program like Excel to create the graphs. Your program does not have to produce the graphs themselves, just the data for the graphs. The data will consist of the number p, the number of stable imprints for that p, and the fraction of unstable imprints for that p. Again, p ranges from 1 to 50.

Repeat the generatePatterns(), imprintPatterns(p), and testPatterns(p) steps several times, each time testing a different set of 50 random patterns (vectors). Average your data over all of these iterations. The more iterations (runs) you do, the smoother your graphs should look.


Basins of attraction (527 students):

In addition to the undergrad work above, estimate the size of the basins of attraction for each imprinted pattern for the number of imprinted patterns p. In part c) in the section testPatterns(p) above where you test for stability of each of the p imprinted patterns, add the following:

1. If the pattern is unstable, set its basin size to 0. In other words, increment the histogram count for basin size 0 for the current value of p.

2. If the pattern is stable, do:

After incrementing the number of stable patterns for the current value of p, iterate through the number of permutations that you want to try (e.g., 5). In this loop, do the following:

a. Generate a permutation of the numbers 1 to 100 by creating an array of the numbers 1 to 100 (or 0 to 99) in random order. This list of numbers will indicate which bits of the pattern to flip and in which order to flip them. You will only need to consider the first 50 elements of this permutation since the maximum basin size is 50.

b. Letting j be the loop variable, go through each of the first 50 elements in the permutation array and:

i. Initialize the neural network to the current pattern k of the p imprinted patterns.

ii. Flip the states of the positions of the neural network given by the first j permutation array elements. That is, change a 1 to a -1 and a -1 to a 1 in these j positions of the neural network. Note that these j positions that you will flip will not necessarily be the actual first j positions in the network.

iii. Go through 10 iterations of updating the neural network. Change each network element according to sigma of its local field h. Update the network elements in random order. For any of the iterations, if none of the neurons change from their previous state, then break from the iterations of updating the neural network.

iv. Check to see if the network is equal to the current imprinted pattern k after these 10 iterations. If the network is different than the imprinted pattern k, then break from the 50 iterations of the permutation array. Otherwise, keep going through the permutation array.

c. The first iteration of the permutation array (as given by j) where the network does not converge to the current imprinted pattern k is the number that estimates the size of the basin of attraction for that imprinted pattern. It is equivalent to the number of bits in the pattern you need to flip until the network does not converge to that pattern. If the network converges for all 50 iterations of the permutation array (that is, it never does not converge), then the size of the basin of attraction for that pattern is 50, since that is the maximum size of a basin of attraction.

Try the above a-c for several different permutations (say, 5) and average the results.

3. Increment the counter of the histogram array keeping track of the basin sizes. You might have a two dimensional array where the first dimension indicates the number of imprinted patterns (given by p), and the second dimension gives the size of the basin of attraction. The actual array element is the count (or frequency) of that basin size for that p. This will keep track of a histogram of basin sizes for each value p.

4. Produce a graph of the histograms for various values of p (even values of p should be enough) as in the following:



Including the first of the two graphs is enough, but including both may boost your grade slightly. The first graph is normalized (i.e., in terms of frequencies of basin sizes), and the second graph is in terms of counts of basin sizes. For normalizing the data, keep in mind the number of permutations you try, the number of different sets of 50 patterns (i.e., number of runs) you try, and the number of imprinted patterns p.


Extra credit

The following items will boost your grade slightly or significantly.

What to submit

Tar or zip your files together and send them to the TA. The directory should include your report (in .pdf) and your code.