Spaces:
Sleeping
Sleeping
| import os | |
| os.system("pip install numpy matplotlib pandas") | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import gradio as gr | |
| import pandas as pd | |
| # --- Your Helper Functions --- | |
| def flatten(img : np.array) -> list[int] : | |
| """Converts a 2D numpy array into a 1D list.""" | |
| new : list[int] = [] | |
| for row in img: | |
| for item in row: | |
| new.append(int(item)) | |
| return new | |
| def sgn(x): | |
| """Sign function.""" | |
| if x < 0: | |
| return -1 | |
| if x == 0: | |
| return 0 | |
| return 1 | |
| # --- Your Hopfield Class (with one bugfix) --- | |
| class Hopfield: | |
| def __init__(self,patts): | |
| self.E : list[int] = [] | |
| self.patts = patts | |
| self.size = (4,4) # Fixed size for reshaping | |
| self.Px :int = len(patts) | |
| self.Py :int = len(patts[0]) | |
| # Initialize weights | |
| self.W : np.array = np.zeros((self.Py,self.Py),dtype=np.float16) | |
| def train(self): | |
| """Trains the network on the patterns provided in __init__.""" | |
| for i in range(self.Py): | |
| for j in range(self.Py): | |
| if i == j: | |
| self.W[i][j] = 0 | |
| continue | |
| # Hebbian rule | |
| self.W[i][j] = (1 / self.Px) * sum([patt[i] * patt[j] for patt in self.patts]) | |
| def Energy(self): | |
| """Returns the list of energy values recorded during updates.""" | |
| return self.E | |
| def update(self,pattern): | |
| """ | |
| Performs one asynchronous update step on the entire pattern. | |
| """ | |
| # Flatten the 2D input pattern to 1D | |
| pattern_flat = flatten(pattern) | |
| # Calculate the new state vector H | |
| H : list[int] = [] | |
| for i in self.W: | |
| # H_i = sgn(sum(W_ij * S_j)) | |
| H.append(sgn(sum([w * s for w,s in zip(i, pattern_flat)]))) | |
| H = np.array(H) | |
| # Calculate the energy of this new state H | |
| E = 0 | |
| for i in range(self.Py): | |
| for j in range(self.Py): | |
| E += float(-0.5 * self.W[i][j] * H[i] * H[j]) | |
| self.E.append(E) | |
| # --- FIX --- | |
| # Use reshape, not resize. Resize can add/remove elements. | |
| # Reshape will fail if H doesn't have 16 elements, which is safer. | |
| return H.reshape(self.size) | |
| # --- Default Patterns for the Gradio App --- | |
| # Pattern 1: 'X' | |
| patt_1_default = [[ 1, -1, -1, 1], | |
| [-1, 1, 1, -1], | |
| [-1, 1, 1, -1], | |
| [ 1, -1, -1, 1]] | |
| # Pattern 2: 'C' | |
| patt_2_default = [[ 1, 1, 1, -1], | |
| [ 1, -1, -1, -1], | |
| [ 1, -1, -1, -1], | |
| [ 1, 1, 1, -1]] | |
| # Pattern 3: 'L' | |
| patt_3_default = [[ 1, -1, -1, -1], | |
| [ 1, -1, -1, -1], | |
| [ 1, -1, -1, -1], | |
| [ 1, 1, 1, 1]] | |
| # Initial (corrupted) shape to test | |
| initial_shape_default = [[ 1, 1, -1, -1], | |
| [ 1, -1, -1, -1], | |
| [ 1, -1, 1, -1], | |
| [ 1, 1, 1, -1]] | |
| # --- Gradio Core Logic --- | |
| def clean_dataframe(df): | |
| """Helper to convert Gradio dataframe to a clean NumPy array.""" | |
| # Fill any empty cells (None) with -1 and convert to int | |
| return df.fillna(-1).to_numpy(dtype=int) | |
| def run_hopfield(patt1_df, patt2_df, patt3_df, initial_shape_df, steps): | |
| """ | |
| The main function for the Gradio interface. | |
| """ | |
| # 1. Clean inputs | |
| p1 = clean_dataframe(patt1_df) | |
| p2 = clean_dataframe(patt2_df) | |
| p3 = clean_dataframe(patt3_df) | |
| initial_shape = clean_dataframe(initial_shape_df) | |
| # 2. Collect patterns to train (ignore empty/all -1 patterns) | |
| patterns_to_train = [] | |
| if np.any(p1 == 1): | |
| patterns_to_train.append(flatten(p1)) | |
| if np.any(p2 == 1): | |
| patterns_to_train.append(flatten(p2)) | |
| if np.any(p3 == 1): | |
| patterns_to_train.append(flatten(p3)) | |
| # 3. Check if any patterns were provided | |
| if not patterns_to_train: | |
| fig_shape = plt.figure() | |
| plt.title("Error: No patterns provided to train.") | |
| plt.axis('off') | |
| fig_energy = plt.figure() | |
| plt.title("Error: No patterns provided to train.") | |
| return fig_shape, fig_energy | |
| # 4. Create and train the model | |
| patts = np.array(patterns_to_train) | |
| model = Hopfield(patts) | |
| model.train() | |
| # 5. Run the evolution | |
| current_shape = initial_shape | |
| for _ in range(int(steps)): | |
| next_shape = model.update(current_shape) | |
| # Check for convergence | |
| if np.array_equal(current_shape, next_shape): | |
| break | |
| current_shape = next_shape | |
| # 6. Generate final shape plot | |
| fig_shape = plt.figure() | |
| plt.imshow(current_shape, cmap='gray', vmin=-1, vmax=1) | |
| plt.title("Final Evolved Shape") | |
| plt.axis('off') | |
| # 7. Generate energy plot | |
| fig_energy = plt.figure() | |
| energy_data = model.Energy() | |
| if energy_data: | |
| plt.plot(list(range(len(energy_data))), energy_data, marker='o') | |
| plt.title("Energy Evolution") | |
| plt.xlabel("Update Step") | |
| plt.ylabel("Energy") | |
| plt.grid(True) | |
| else: | |
| plt.title("Energy (No Updates Run)") | |
| return fig_shape, fig_energy | |
| # --- Gradio Interface --- | |
| with gr.Blocks(theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# 🧠 Hopfield Network Simulator") | |
| gr.Markdown( | |
| "Define up to 3 patterns (1 for 'on', -1 for 'off'). " | |
| "The network will learn them. Then, draw an 'Initial Shape' " | |
| "and see if the network can evolve it into one of the patterns it learned." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### 1. Define Patterns to Memorize") | |
| # Set headers to be invisible, type to pandas for .fillna | |
| patt1_in = gr.Dataframe( | |
| value=patt_1_default, | |
| label="Pattern 1", | |
| headers=None, | |
| datatype="number", | |
| col_count=4, | |
| row_count=4, | |
| type="pandas" | |
| ) | |
| patt2_in = gr.Dataframe( | |
| value=patt_2_default, | |
| label="Pattern 2", | |
| headers=None, | |
| datatype="number", | |
| col_count=4, | |
| row_count=4, | |
| type="pandas" | |
| ) | |
| patt3_in = gr.Dataframe( | |
| value=patt_3_default, | |
| label="Pattern 3", | |
| headers=None, | |
| datatype="number", | |
| col_count=4, | |
| row_count=4, | |
| type="pandas" | |
| ) | |
| with gr.Column(): | |
| gr.Markdown("### 2. Set Initial Shape & Run") | |
| initial_in = gr.Dataframe( | |
| value=initial_shape_default, | |
| label="Initial Shape (Test Pattern)", | |
| headers=None, | |
| datatype="number", | |
| col_count=4, | |
| row_count=4, | |
| type="pandas" | |
| ) | |
| steps_in = gr.Slider( | |
| minimum=1, | |
| maximum=10, | |
| value=5, | |
| step=1, | |
| label="Max Evolution Steps" | |
| ) | |
| run_btn = gr.Button("Run Evolution", variant="primary") | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### 3. Results") | |
| shape_out = gr.Plot(label="Final Evolved Shape") | |
| with gr.Column(): | |
| gr.Markdown("### 4. Diagnostics") | |
| energy_out = gr.Plot(label="Energy Evolution") | |
| # Connect the button to the function | |
| run_btn.click( | |
| fn=run_hopfield, | |
| inputs=[patt1_in, patt2_in, patt3_in, initial_in, steps_in], | |
| outputs=[shape_out, energy_out] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |