├── assets ├── model.gif ├── result_0.png ├── result_1.png ├── weights.png ├── result_0_custom.png └── energy.svg ├── LICENSE ├── network.py ├── train.py ├── train_custom.py └── README.md /assets/model.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crypto-code/Hopfield-Network/HEAD/assets/model.gif -------------------------------------------------------------------------------- /assets/result_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crypto-code/Hopfield-Network/HEAD/assets/result_0.png -------------------------------------------------------------------------------- /assets/result_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crypto-code/Hopfield-Network/HEAD/assets/result_1.png -------------------------------------------------------------------------------- /assets/weights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crypto-code/Hopfield-Network/HEAD/assets/weights.png -------------------------------------------------------------------------------- /assets/result_0_custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crypto-code/Hopfield-Network/HEAD/assets/result_0_custom.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Atin Sakkeer Hussain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /network.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from matplotlib import pyplot as plt 3 | import matplotlib.cm as cm 4 | from tqdm import tqdm 5 | 6 | class HopfieldNetwork(object): 7 | def train_weights(self, train_data): 8 | print("Start to train weights...") 9 | num_data = len(train_data) 10 | self.num_neuron = train_data[0].shape[0] 11 | 12 | # initialize weights 13 | W = np.zeros((self.num_neuron, self.num_neuron)) 14 | rho = np.sum([np.sum(t) for t in train_data]) / (num_data*self.num_neuron) 15 | 16 | # Hebb rule 17 | for i in tqdm(range(num_data)): 18 | t = train_data[i] - rho 19 | W += np.outer(t, t) 20 | 21 | # Make diagonal element of W into 0 22 | diagW = np.diag(np.diag(W)) 23 | W = W - diagW 24 | W /= num_data 25 | 26 | self.W = W 27 | 28 | def predict(self, data, num_iter=20, threshold=0, asyn=False): 29 | print("Start to predict...") 30 | self.num_iter = num_iter 31 | self.threshold = threshold 32 | self.asyn = asyn 33 | 34 | # Copy to avoid call by reference 35 | copied_data = np.copy(data) 36 | 37 | # Define predict list 38 | predicted = [] 39 | for i in tqdm(range(len(data))): 40 | predicted.append(self._run(copied_data[i])) 41 | return predicted 42 | 43 | def _run(self, init_s): 44 | if self.asyn==False: 45 | """ 46 | Synchronous update 47 | """ 48 | # Compute initial state energy 49 | s = init_s 50 | 51 | e = self.energy(s) 52 | 53 | # Iteration 54 | for i in range(self.num_iter): 55 | # Update s 56 | s = np.sign(self.W @ s - self.threshold) 57 | # Compute new state energy 58 | e_new = self.energy(s) 59 | 60 | # s is converged 61 | if e == e_new: 62 | return s 63 | # Update energy 64 | e = e_new 65 | return s 66 | else: 67 | """ 68 | Asynchronous update 69 | """ 70 | # Compute initial state energy 71 | s = init_s 72 | e = self.energy(s) 73 | 74 | # Iteration 75 | for i in range(self.num_iter): 76 | for j in range(100): 77 | # Select random neuron 78 | idx = np.random.randint(0, self.num_neuron) 79 | # Update s 80 | s[idx] = np.sign(self.W[idx].T @ s - self.threshold) 81 | 82 | # Compute new state energy 83 | e_new = self.energy(s) 84 | 85 | # s is converged 86 | if e == e_new: 87 | return s 88 | # Update energy 89 | e = e_new 90 | return s 91 | 92 | 93 | def energy(self, s): 94 | return -0.5 * s @ self.W @ s + np.sum(s * self.threshold) 95 | 96 | def plot_weights(self, path): 97 | plt.figure(figsize=(6, 5)) 98 | w_mat = plt.imshow(self.W, cmap=cm.coolwarm) 99 | plt.colorbar(w_mat) 100 | plt.title("Network Weights") 101 | plt.tight_layout() 102 | plt.savefig(path+"/weights.png") 103 | plt.show() 104 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | np.random.seed(1) 3 | from matplotlib import pyplot as plt 4 | import skimage.data 5 | from skimage.color import rgb2gray 6 | from skimage.filters import threshold_mean 7 | from skimage.transform import resize 8 | import network 9 | import os 10 | 11 | # Helper Functions 12 | def get_corrupted_input(input, corruption_level): 13 | corrupted = np.copy(input) 14 | inv = np.random.binomial(n=1, p=corruption_level, size=len(input)) 15 | for i, v in enumerate(input): 16 | if inv[i]: 17 | corrupted[i] = -1 * v 18 | return corrupted 19 | 20 | def reshape(data): 21 | dim = int(np.sqrt(len(data))) 22 | data = np.reshape(data, (dim, dim)) 23 | return data 24 | def split(l, n): 25 | for i in range(0,len(l), n): 26 | yield l[i:i+n] 27 | 28 | def plot(data, test, predicted, figsize=(5, 6)): 29 | data = [reshape(d) for d in data] 30 | test = [reshape(d) for d in test] 31 | predicted = [reshape(d) for d in predicted] 32 | if not os.path.exists('results'): 33 | os.mkdir('results') 34 | count=0 35 | file_count=0 36 | for d in split(data, 4): 37 | if not len(d)is 1: 38 | fig, axarr = plt.subplots(len(d), 3) 39 | for i in range(len(d)): 40 | if i==0: 41 | axarr[i, 0].set_title('Train data') 42 | axarr[i, 1].set_title("Input data") 43 | axarr[i, 2].set_title('Output data') 44 | 45 | axarr[i, 0].imshow(data[count]) 46 | axarr[i, 0].axis('off') 47 | axarr[i, 1].imshow(test[count]) 48 | axarr[i, 1].axis('off') 49 | axarr[i, 2].imshow(predicted[count]) 50 | axarr[i, 2].axis('off') 51 | count = count+1 52 | 53 | plt.tight_layout() 54 | plt.savefig("results/result_"+str(file_count)+".png") 55 | file_count=file_count+1 56 | plt.show() 57 | else: 58 | fig, axarr = plt.subplots(1, 3) 59 | axarr[0].set_title('Train data') 60 | axarr[1].set_title("Input data") 61 | axarr[2].set_title('Output data') 62 | 63 | axarr[0].imshow(data[count]) 64 | axarr[0].axis('off') 65 | axarr[1].imshow(test[count]) 66 | axarr[1].axis('off') 67 | axarr[2].imshow(predicted[count]) 68 | axarr[2].axis('off') 69 | count = count+1 70 | plt.tight_layout() 71 | plt.savefig("results/result_"+str(file_count)+".png") 72 | file_count=file_count+1 73 | plt.show() 74 | 75 | def preprocessing(img, w=128, h=128): 76 | # Resize image 77 | img = resize(img, (w,h), mode='reflect') 78 | 79 | # Thresholding 80 | thresh = threshold_mean(img) 81 | binary = img > thresh 82 | shift = 2*(binary*1)-1 # Boolian to int 83 | 84 | # Reshape 85 | flatten = np.reshape(shift, (w*h)) 86 | return flatten 87 | 88 | def main(): 89 | # Load data 90 | 91 | import cv2 92 | import glob 93 | img_dir = "train/" # Enter Directory of all images 94 | data_path = os.path.join(img_dir,'*g') 95 | files = glob.glob(data_path) 96 | data = [] 97 | for f1 in files: 98 | img = rgb2gray(cv2.imread(f1)) 99 | data.append(img) 100 | 101 | # Preprocessing 102 | print("Start to data preprocessing...") 103 | data = [preprocessing(d) for d in data] 104 | 105 | # Create Hopfield Network Model 106 | model = network.HopfieldNetwork() 107 | model.train_weights(data) 108 | 109 | # Generate testset 110 | test = [get_corrupted_input(d, 0.3) for d in data] 111 | 112 | predicted = model.predict(test, threshold=0, asyn=False) 113 | print("Show prediction results...") 114 | plot(data, test, predicted) 115 | #print("Show network weights matrix...") 116 | #model.plot_weights("results/") 117 | 118 | if __name__ == '__main__': 119 | main() 120 | -------------------------------------------------------------------------------- /train_custom.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | np.random.seed(1) 3 | from matplotlib import pyplot as plt 4 | import skimage.data 5 | from skimage.color import rgb2gray 6 | from skimage.filters import threshold_mean 7 | from skimage.transform import resize 8 | import network 9 | import os 10 | 11 | # Helper Functions 12 | def get_corrupted_input(input, corruption_level): 13 | corrupted = np.copy(input) 14 | inv = np.random.binomial(n=1, p=corruption_level, size=len(input)) 15 | for i, v in enumerate(input): 16 | if inv[i]: 17 | corrupted[i] = -1 * v 18 | return corrupted 19 | 20 | def reshape(data): 21 | dim = int(np.sqrt(len(data))) 22 | data = np.reshape(data, (dim, dim)) 23 | return data 24 | def split(l, n): 25 | for i in range(0,len(l), n): 26 | yield l[i:i+n] 27 | 28 | def plot(data, test, predicted): 29 | data = [reshape(d) for d in data] 30 | test = [reshape(d) for d in test] 31 | predicted = [reshape(d) for d in predicted] 32 | if not os.path.exists('results_custom'): 33 | os.mkdir('results_custom') 34 | count=0 35 | file_count=0 36 | for d in split(data, 4): 37 | if not len(d)is 1: 38 | fig, axarr = plt.subplots(len(d), 3) 39 | for i in range(len(d)): 40 | if i==0: 41 | axarr[i, 0].set_title('Train data') 42 | axarr[i, 1].set_title("Input data") 43 | axarr[i, 2].set_title('Output data') 44 | 45 | axarr[i, 0].imshow(data[count]) 46 | axarr[i, 0].axis('off') 47 | axarr[i, 1].imshow(test[count]) 48 | axarr[i, 1].axis('off') 49 | axarr[i, 2].imshow(predicted[count]) 50 | axarr[i, 2].axis('off') 51 | count = count+1 52 | 53 | plt.tight_layout() 54 | plt.savefig("results_custom/result_"+str(file_count)+".png") 55 | file_count=file_count+1 56 | plt.show() 57 | else: 58 | fig, axarr = plt.subplots(1, 3) 59 | axarr[0].set_title('Train data') 60 | axarr[1].set_title("Input data") 61 | axarr[2].set_title('Output data') 62 | 63 | axarr[0].imshow(data[count]) 64 | axarr[0].axis('off') 65 | axarr[1].imshow(test[count]) 66 | axarr[1].axis('off') 67 | axarr[2].imshow(predicted[count]) 68 | axarr[2].axis('off') 69 | count = count+1 70 | plt.tight_layout() 71 | plt.savefig("results_custom/result_"+str(file_count)+".png") 72 | file_count=file_count+1 73 | plt.show() 74 | 75 | def preprocessing(img, w=128, h=128): 76 | # Resize image 77 | img = resize(img, (w,h), mode='reflect') 78 | 79 | # Thresholding 80 | thresh = threshold_mean(img) 81 | binary = img > thresh 82 | shift = 2*(binary*1)-1 # Boolian to int 83 | 84 | # Reshape 85 | flatten = np.reshape(shift, (w*h)) 86 | return flatten 87 | 88 | def main(): 89 | # Load data 90 | 91 | import cv2 92 | import glob 93 | img_dir = "train_custom/" # Enter Directory of all images 94 | data_path = os.path.join(img_dir,'*g') 95 | files = glob.glob(data_path) 96 | data = [] 97 | for f1 in files: 98 | img = rgb2gray(cv2.imread(f1)) 99 | data.append(img) 100 | 101 | # Preprocessing 102 | print("Start to data preprocessing...") 103 | data = [preprocessing(d) for d in data] 104 | 105 | # Create Hopfield Network Model 106 | model = network.HopfieldNetwork() 107 | model.train_weights(data) 108 | 109 | # Generate testset 110 | img_dir = "test_custom/" # Enter Directory of all images 111 | data_path = os.path.join(img_dir,'*g') 112 | files = glob.glob(data_path) 113 | test = [] 114 | # Since same name order will be the same 115 | for f1 in files: 116 | img = rgb2gray(cv2.imread(f1)) 117 | test.append(img) 118 | test = [preprocessing(d) for d in test] 119 | 120 | predicted = model.predict(test, threshold=0, asyn=False) 121 | print("Show prediction results...") 122 | plot(data, test, predicted) 123 | #print("Show network weights matrix...") 124 | #model.plot_weights("results_custom/") 125 | 126 | if __name__ == '__main__': 127 | main() 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hopfield-Network 2 | Create a Hopfield Network for Image Reconstruction 3 | 4 | ## What is a Hopefield Network ? 5 | 6 | At its core a Hopfield Network is a model that can reconstruct data after being fed with corrupt versions of the same data. This neural network proposed by Hopfield in 1982 can be seen as a network with associative memory. 7 | 8 | Lets say you hear a melody of a song and suddenly remember when you where on a concert hearing your favorite band playing just that song. That is associative memory. You get an input, which is a fragment of a memory you have stored in your brain, and get an output of the entire memory you have stored in your brain. 9 | 10 |

11 |

12 | 13 | The Hopfield network here works in the same way. When the network is presented with an input, i.e. put in a state, the networks nodes will start to update and converge to a state which is a previously stored pattern. The learning algorithm “stores” a given pattern in the network by adjusting the weights. There is off course a limit to how many “memories” you can store correctly in the network, and empirical results show that for every pattern to be stored correctly, the number of patterns has to between 10-20% compared to the number of nodes. 14 | 15 | ## How it Works ? 16 | 17 | The units in Hopfield nets are binary threshold units, i.e. the units only take on two different values for their states and the value is determined by whether or not the units' input exceeds their threshold. Hopfield nets normally have units that take on values of 1 or -1. 18 | 19 | Hopfield nets have a scalar value associated with each state of the network, referred to as the "energy", E, of the network, where: 20 | 21 |

22 |

23 | 24 | This quantity is called "energy" because it either decreases or stays the same upon network units being updated. Furthermore, under repeated updating the network will eventually converge to a state which is a local minimum in the energy function. 25 | 26 | Training a Hopfield net involves lowering the energy of states that the net should "remember". This allows the net to serve as a content addressable memory system, that is to say, the network will converge to a "remembered" state if it is given only part of the state. The net can be used to recover from a distorted input to the trained state that is most similar to that input. This is called associative memory because it recovers memories on the basis of similarity. For example, if we train a Hopfield net with five units so that the state (1, -1, 1, -1, 1) is an energy minimum, and we give the network the state (1, -1, -1, -1, 1) it will converge to (1, -1, 1, -1, 1). Thus, the network is properly trained when the energy of states which the network should remember are local minima. 27 | 28 | ## Usage 29 | 30 | - To train a Hopfield Network on a dataset of images, first place the images in the /train folder (A few images are already provided). Then run train.py script. 31 | 32 | ### Result: 33 |

34 |

35 | 36 |

37 |

38 | 39 | This script trains the network on the provided images and tests image recounstruction by using the "images+random noise" as input. As seen above the images are reconstructed almost perfectly. 40 | 41 | 42 | - To train a Hopfield Network to reconstruct images from custom noisy images, first place the train images in the /train_custom folder and the noisy images in /test_custom folder (A few images are already provided). Then run train_custom.py script. 43 | 44 | **Note: The noisy images should have same name as its corresponding train image** 45 | 46 | ### Result 47 | 48 |

49 |

50 | 51 | As you can see above the unwanted parts of the images are removed. In the first image the **&** is removed and in the second the man's glasses is removed almost perfectly. 52 | 53 | 54 | ### Model Weights: 55 | **Note: If you want to plot the weights of the network, just uncomment line:116 in train.py and line:124 in train_custom.py** 56 |

57 |

58 | 59 | # G00D LUCK 60 | 61 | For doubts email me at: 62 | atinsaki@gmail.com 63 | -------------------------------------------------------------------------------- /assets/energy.svg: -------------------------------------------------------------------------------- 1 | 2 | {\displaystyle E=-{\frac {1}{2}}\sum _{i,j}{w_{ij}{s_{i}}{s_{j}}}+\sum _{i}{\theta _{i}}{s_{i}}} 3 | 18 | 66 | --------------------------------------------------------------------------------