sigmoidneuron123's picture
Update app.py
cee6e5e verified
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()