NeonSamurai commited on
Commit
861b776
·
verified ·
1 Parent(s): 4c30ee5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +459 -458
app.py CHANGED
@@ -1,458 +1,459 @@
1
- import streamlit as st
2
- import networkx as nx
3
-
4
- import pandas as pd
5
- import numpy as np
6
- import matplotlib.pyplot as plt
7
-
8
- import io
9
-
10
- import base64
11
-
12
- from sklearn.datasets import make_blobs, make_circles
13
- from sklearn.preprocessing import StandardScaler
14
- from sklearn.model_selection import train_test_split
15
-
16
- from mlxtend.plotting import plot_decision_regions
17
-
18
- import keras
19
- from keras.optimizers import SGD
20
- from keras.models import Sequential
21
- from keras.layers import Input, Dense
22
- from keras.losses import BinaryCrossentropy
23
- from keras.regularizers import l2, l1
24
-
25
- st.set_page_config(layout='wide')
26
-
27
- def encode_image(image_path):
28
- with open(image_path, "rb") as image_file:
29
- return base64.b64encode(image_file.read()).decode()
30
-
31
- def add_bg_from_local(image_file):
32
- encoded_string = encode_image(image_file)
33
- st.markdown(
34
- f"""
35
- <style>
36
- .stApp {{
37
- background-image: url(data:image/png;base64,{encoded_string});
38
- background-size: cover;
39
- background-position: center;
40
- background-repeat: no-repeat;
41
- background-attachment: fixed;
42
- }}
43
- </style>
44
- """,
45
- unsafe_allow_html=True
46
- )
47
-
48
- add_bg_from_local("Images/bkg12.jpg")
49
-
50
- # Session state for tracking training process
51
- for key, value in {
52
- "training": False,
53
- "num_hidden_layers": 0,
54
- "hidden_layer_neurons": [],
55
- "prev_params": {},
56
- }.items():
57
- if key not in st.session_state:
58
- st.session_state[key] = value
59
-
60
- def reset_session():
61
- st.session_state.clear()
62
-
63
-
64
- st.title("Neural Network Playground")
65
-
66
- # Sidebar for paramters
67
- st.sidebar.title("Configure & Train Model")
68
- problem_type = st.sidebar.selectbox("Problem Type", ["Classification",]) #"Regression"])
69
- dataset_type = None
70
- if problem_type == "Classification":
71
- dataset_type = st.sidebar.selectbox("Select Dataset Type", ["Circle", "Gaussian", "Exclusive OR"])
72
- # else:
73
- # dataset_type = st.sidebar.selectbox("Select Dataset Type", ["Plane", "Gaussian Plane"])
74
- col1, col2 = st.sidebar.columns(2)
75
- with col1:
76
- learning_rate = st.selectbox("Learning Rate", [0.00001,0.0001,0.001,0.01,0.03,0.1,0.3,1,3,10])
77
- with col2:
78
- activation_function = st.selectbox("Activation Function", ["ReLU", "Sigmoid", "Tanh"])
79
-
80
- col1, col2 = st.sidebar.columns(2)
81
- with col1:
82
- regularization_type = st.selectbox("Regularization", ["None", "L1", "L2"])
83
- with col2:
84
- regularization_rate = st.selectbox("Regularization Rate", [0.0,0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1, 3, 10], disabled=(regularization_type == "None"))
85
-
86
- train_to_test_ratio = st.sidebar.slider("Train-to-Test Ratio (%)", 10, 90, 20, 10) / 100
87
- noise_level_slider = st.sidebar.slider("Noise Level", 0, 50, step=5)
88
- batch_size = st.sidebar.slider("Batch Size", 1, 30, 10)
89
-
90
- if st.sidebar.button("🔄 Reset Session"):
91
- reset_session()
92
- st.rerun()
93
-
94
- # min noise
95
- min_noise = 0.09
96
-
97
- # Scaling the noise level to range [0.02, 0.2]
98
- noise_level = min_noise + (noise_level_slider / 50) * (0.2 - min_noise)
99
-
100
- # Store current parameter values in a dictionary
101
- current_params = {
102
- "dataset_type": dataset_type,
103
- "learning_rate": learning_rate,
104
- "regularization_type": regularization_type,
105
- "regularization_rate": regularization_rate,
106
- "activation_function": activation_function,
107
- "train_to_test_ratio": train_to_test_ratio,
108
- "batch_size": batch_size,
109
- "noise_level": noise_level
110
- }
111
-
112
- gaussian_noise = 2.0 + ((noise_level_slider - 1) / 50) ** 2 * (10)
113
-
114
- def make_xor(n_samples=250, noise=0):
115
-
116
- # Base spread ensures some separation even when noise = 0
117
- base_spread = 2.0
118
- min_offset = 0.1 # Prevents tight clustering at corners
119
-
120
- # Generate XOR quadrants
121
- X1 = np.random.uniform(-base_spread, -min_offset, (n_samples, 2)) # Bottom-left
122
- X2 = np.random.uniform(min_offset, base_spread, (n_samples, 2)) # Top-right
123
- X3_x = np.random.uniform(-base_spread, -min_offset, (n_samples, 1)) # Top-left (x)
124
- X3_y = np.random.uniform(min_offset, base_spread, (n_samples, 1)) # Top-left (y)
125
- X3 = np.hstack([X3_x, X3_y])
126
-
127
- X4_x = np.random.uniform(min_offset, base_spread, (n_samples, 1)) # Bottom-right (x)
128
- X4_y = np.random.uniform(-base_spread, -min_offset, (n_samples, 1)) # Bottom-right (y)
129
- X4 = np.hstack([X4_x, X4_y])
130
-
131
- X = np.vstack([X1, X2, X3, X4])
132
-
133
- # Apply smooth noise scaling
134
- if noise > 0:
135
- noise_scale = 0.05 + (noise / 100) # Small increase for gradual effect
136
- X += np.random.randn(*X.shape) * noise_scale
137
-
138
- # Define XOR labels: (1 if x and y have same sign, else 0)
139
- y = np.logical_xor(X[:, 0] > 0, X[:, 1] > 0).astype(int)
140
-
141
- return X, y
142
-
143
- # Total dataset size
144
- total_samples = 800
145
-
146
- # Calculate training set size
147
- train_size = int(total_samples * train_to_test_ratio)
148
-
149
- def get_dataset(dataset_type, total_samples, noise_level, gaussian_noise, noise_level_slider):
150
- # Dataset generators
151
- dataset_generators = {
152
- "Gaussian": lambda: make_blobs(n_samples=total_samples, centers=2, n_features=2, cluster_std=gaussian_noise, random_state=45),
153
- "Circle": lambda: make_circles(n_samples=total_samples, shuffle=True, noise=noise_level, factor=0.2),
154
- "Exclusive OR": lambda: make_xor(n_samples=total_samples, noise=noise_level_slider),
155
- #"Spiral": lambda: make_spiral(n_samples=total_samples, noise=noise_level_slider),
156
- }
157
- return dataset_generators.get(dataset_type, lambda: (None, None))()
158
- # Fetch dataset
159
- if problem_type == "Classification":
160
- fv, cv = get_dataset(dataset_type, total_samples, noise_level, gaussian_noise, noise_level_slider)
161
-
162
-
163
- # Functions for modifying hidden layers
164
- def add_layer():
165
- if st.session_state.num_hidden_layers < 6:
166
- st.session_state.num_hidden_layers += 1
167
- st.session_state.hidden_layer_neurons.append(1)
168
-
169
- def remove_layer():
170
- if st.session_state.num_hidden_layers > 0 and st.session_state.hidden_layer_neurons:
171
- st.session_state.num_hidden_layers -= 1
172
- st.session_state.hidden_layer_neurons.pop()
173
-
174
- # Functions for modifying neurons in each layer
175
- def increase_neurons(layer_idx):
176
- if st.session_state.hidden_layer_neurons[layer_idx] < 8:
177
- st.session_state.hidden_layer_neurons[layer_idx] += 1
178
-
179
- def decrease_neurons(layer_idx):
180
- if st.session_state.hidden_layer_neurons[layer_idx] > 1:
181
- st.session_state.hidden_layer_neurons[layer_idx] -= 1
182
-
183
- col1, col2, col3 = st.columns([2, 2, 2])
184
-
185
- with col1:
186
- st.subheader("Select Input Features")
187
-
188
- # Compute new features
189
- std = StandardScaler()
190
- X = std.fit_transform(fv)
191
- x1, x2 = X[:, 0], X[:, 1]
192
-
193
- # Update feature selection
194
- available_features = ["X1", "X2"]
195
-
196
- st.markdown("""
197
- <style>
198
- div[data-testid="stCheckbox"] {
199
- background-color: #252830;
200
- border-radius: 8px;
201
- padding: 8px;
202
- margin-bottom: 5px;
203
- color: white;
204
- }
205
- div[data-testid="stCheckbox"] label {
206
- font-size: 16px;
207
- font-weight: bold;
208
- color: white;
209
- }
210
- </style>
211
- """, unsafe_allow_html=True)
212
-
213
- selected_features = [feature for feature in available_features if st.checkbox(feature, value = st.session_state.get(feature, feature in ["X1", "X2"]), key=feature)]
214
- st.session_state.selected_features = selected_features
215
- num_inputs = len(selected_features)
216
-
217
- # Map feature names to actual values
218
- feature_mapping = {
219
- "X1": x1,
220
- "X2": x2,
221
- }
222
-
223
- if problem_type == 'Classification':
224
- # Ensure a balanced split (Stratified Sampling)
225
- x_train, x_test, y_train, y_test = train_test_split(
226
- fv, cv,
227
- test_size=1-train_to_test_ratio,
228
- stratify=cv,
229
- )
230
- else:
231
- # Ensure a balanced split
232
- x_train, x_test, y_train, y_test = train_test_split(
233
- fv, cv,
234
- test_size=1-train_to_test_ratio
235
- )
236
- with col2:
237
- # Visualize dataset
238
- st.subheader("Dataset Preview")
239
- fig, ax = plt.subplots(figsize=(3, 3))
240
- scatter = ax.scatter(x_train[:, 0], x_train[:, 1], c=y_train, cmap="coolwarm", edgecolors="k", alpha=0.7)
241
- ax.set_xticks([])
242
- ax.set_yticks([])
243
- ax.set_facecolor("#f0f0f0")
244
-
245
- st.pyplot(fig)
246
-
247
- num_outputs = 1
248
- with col3:
249
- st.subheader("Hidden Layers")
250
- col1, col2 = st.columns([1, 1])
251
- with col1:
252
- st.button("➕ Add Layer", on_click=add_layer)
253
- with col2:
254
- st.button("➖ Remove Layer", on_click=remove_layer)
255
-
256
- st.write("**Adjust Neurons in Each Layer:**")
257
- for i in range(st.session_state.num_hidden_layers):
258
- col1, col2, col3 = st.columns([1, 2, 1])
259
- with col1:
260
- st.button("➖", key=f"dec_neuron_{i}", on_click=decrease_neurons, args=(i,))
261
- with col2:
262
- st.markdown(f"**Layer {i+1}: {st.session_state.hidden_layer_neurons[i]} neurons**")
263
- with col3:
264
- st.button("➕", key=f"inc_neuron_{i}", on_click=increase_neurons, args=(i,))
265
-
266
- # Stack selected features for training
267
- selected_data = np.column_stack([feature_mapping[feature] for feature in selected_features])
268
-
269
-
270
- # Function to draw the neural network visually
271
- def draw_nn(selected_features, hidden_layer_neurons, num_outputs):
272
- G = nx.DiGraph()
273
-
274
- # Define layers dynamically
275
- input_layer = selected_features # Match node names with feature names
276
- hidden_layers = []
277
- if st.session_state.num_hidden_layers > 0:
278
- hidden_layers = [[f"hl{i+1}_{j+1}" for j in range(hidden_layer_neurons[i])] for i in range(st.session_state.num_hidden_layers)]
279
- output_layer = ["y1"] # Single output neuron
280
-
281
- layers = [input_layer] + hidden_layers + [output_layer]
282
-
283
- # Add nodes and assign colors
284
- node_colors = {}
285
- input_color = "lightgreen"
286
- hidden_color = "lightblue"
287
- output_color = "salmon"
288
-
289
- # Add nodes
290
- # for layer_idx, layer in enumerate(layers):
291
- # for node in layer:
292
- # G.add_node(node, layer=layer_idx, edgecolors='black')
293
- for layer_idx, layer in enumerate(layers):
294
- for node in layer:
295
- G.add_node(node, layer=layer_idx, edgecolors='black')
296
- if layer_idx == 0:
297
- node_colors[node] = input_color # Input layer
298
- elif layer_idx == len(layers) - 1:
299
- node_colors[node] = output_color # Output layer
300
- else:
301
- node_colors[node] = hidden_color # Hidden layers
302
-
303
- # Add edges (fully connected between layers)
304
- for i in range(len(layers) - 1):
305
- for node1 in layers[i]:
306
- for node2 in layers[i + 1]:
307
- G.add_edge(node1, node2)
308
-
309
- # Graph Layout
310
- pos = nx.multipartite_layout(G, subset_key="layer")
311
- fig, ax = plt.subplots(figsize=(12, 4))
312
-
313
- # Style updates for TensorFlow Playground look
314
- fig.patch.set_alpha(0)
315
- ax.set_facecolor("#252830") # Dark background
316
- ax.patch.set_alpha(1)
317
-
318
- # Get color list
319
- color_list = [node_colors[node] for node in G.nodes]
320
-
321
- nx.draw(G, pos, with_labels=True, node_color=color_list, edge_color="white", edgecolors = "black",
322
- node_size=800, font_size=7.5, ax=ax, width=0.4, font_color="black", font_weight="bold")
323
-
324
- return fig
325
-
326
- def create_ann_model(input_dim, hidden_layers, neurons_per_layer):
327
- model = Sequential()
328
- model.add(Input(shape=(input_dim,))) # Input layer
329
-
330
- reg = None
331
- if regularization_type == "L1":
332
- reg = l1(regularization_rate)
333
- elif regularization_type == "L2":
334
- reg = l2(regularization_rate)
335
-
336
- # Add hidden layers
337
- for neurons in neurons_per_layer:
338
- model.add(Dense(neurons, activation=activation_function.lower(), kernel_regularizer=reg))
339
-
340
- # Output layer
341
- model.add(Dense(1, activation='sigmoid'))
342
-
343
- # Compile the model with explicit learning rate
344
- optimizer = SGD(learning_rate=learning_rate)
345
- model.compile(
346
- optimizer=optimizer,
347
- loss=BinaryCrossentropy(),
348
- metrics=['accuracy']
349
- )
350
- return model
351
-
352
- def plot_decision_boundary(model, x_train, y_train):
353
- plt.figure(figsize=(6, 4))
354
- plot_decision_regions(x_train, y_train, clf=model, legend=2)
355
- #plt.title('Decision Boundary')
356
- return plt
357
-
358
- class LossPlotCallback(keras.callbacks.Callback):
359
- def __init__(self, X, y, display_epochs=10):
360
- super().__init__()
361
- self.loss_df = pd.DataFrame(columns=["Epoch", "Train Loss", "Val Loss"])
362
- #self.display_epochs = display_epochs
363
- self.X = X
364
- self.y = y
365
- self.plot_placeholder = st.empty() # SINGLE container to update dynamically
366
-
367
- def on_epoch_end(self, epoch, logs=None):
368
- # Append new train and validation loss values
369
- new_row = pd.DataFrame({
370
- "Epoch": [epoch + 1],
371
- "Train Loss": [logs['loss']],
372
- "Val Loss": [logs['val_loss']]
373
- })
374
- self.loss_df = pd.concat([self.loss_df, new_row], ignore_index=True)
375
-
376
- with self.plot_placeholder.container():
377
- col1, col2 = st.columns([1, 1])
378
-
379
- # Left Column: Decision Surface
380
- with col1:
381
- st.write("### Decision Boundary")
382
- fig1 = plot_decision_boundary(ann_model, selected_data, cv)
383
- st.pyplot(fig1, clear_figure=True)
384
-
385
-
386
- # Right Column: Loss Plot
387
- with col2:
388
- st.write("### Training vs Validation Loss")
389
- fig2, ax = plt.subplots(figsize=(6, 4), dpi=100)
390
- ax.plot(self.loss_df["Epoch"], self.loss_df["Train Loss"], marker='o', markersize=1, linestyle='-', color='b', label="Train Loss")
391
-
392
- if "Val Loss" in self.loss_df.columns and self.loss_df["Val Loss"].notna().any():
393
- ax.plot(self.loss_df["Epoch"], self.loss_df["Val Loss"], marker='s',markersize=1, linestyle='--', color='r', label="Val Loss")
394
-
395
- ax.set_xlabel("Epochs", fontsize=12, fontweight='bold')
396
- ax.set_ylabel("Loss", fontsize=12, fontweight='bold')
397
-
398
- #ax.set_title("Training vs Validation Loss", fontsize=14, fontweight='bold')
399
-
400
- ax.legend(fontsize=10)
401
-
402
- ax.grid(True, linestyle='--', alpha=0.6)
403
- ax.spines['top'].set_visible(False)
404
- ax.spines['right'].set_visible(False)
405
-
406
- ax.set_xticks(range(1, len(self.loss_df) + 1),)
407
- plt.xticks(rotation=45)
408
- #ax.set_yticks(range(0, 1.0, 0.1))
409
- st.pyplot(fig2, clear_figure=True)
410
-
411
-
412
- if current_params != st.session_state.prev_params:
413
- st.session_state.training = False # Stop training when a parameter changes
414
- st.session_state.prev_params = current_params
415
-
416
- # Start/Stop Buttons
417
- col1, col2 = st.columns([1, 1])
418
- with col1:
419
- if st.button("▶️ Start Training"):
420
- st.session_state.training = True
421
- st.session_state.model_trained = False
422
-
423
- with col2:
424
- if st.button("⏹️ Stop Training"):
425
- st.session_state.training = False
426
-
427
- # Render the neural network visualization
428
- st.write("### Logical Structure of the Neural Network")
429
- st.pyplot(draw_nn(selected_features, st.session_state.hidden_layer_neurons, num_outputs))
430
-
431
- # Train Model if Start is clicked
432
- if st.session_state.training:
433
- # Train the model and track loss in a DataFrame
434
- ann_model = create_ann_model(
435
- len(selected_features),
436
- st.session_state.num_hidden_layers,
437
- st.session_state.hidden_layer_neurons
438
- )
439
-
440
- st.session_state.model_trained = True
441
-
442
- loss_plot_callback = LossPlotCallback(X=selected_data, y=cv)
443
-
444
- # Capture model summary
445
- model_summary = io.StringIO()
446
- ann_model.summary(print_fn=lambda x: model_summary.write(x + "\n"))
447
-
448
- # Display ANN model summary in Streamlit
449
- st.subheader("Artificial Neural Network Model Summary")
450
- st.code(model_summary.getvalue(), language="plaintext")
451
-
452
- history = ann_model.fit(
453
- x_train, y_train,
454
- epochs=999999,
455
- validation_data= (x_test, y_test),
456
- batch_size=batch_size,
457
- callbacks=[loss_plot_callback],
458
- )
 
 
1
+ import streamlit as st
2
+ import networkx as nx
3
+
4
+ import pandas as pd
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+
8
+ import io
9
+
10
+ from sklearn.datasets import make_blobs, make_circles
11
+ from sklearn.preprocessing import StandardScaler
12
+ from sklearn.model_selection import train_test_split
13
+
14
+ from mlxtend.plotting import plot_decision_regions
15
+
16
+ import keras
17
+ from keras.optimizers import SGD
18
+ from keras.models import Sequential
19
+ from keras.layers import Input, Dense
20
+ from keras.losses import BinaryCrossentropy
21
+ from keras.regularizers import l2, l1
22
+
23
+ st.set_page_config(layout='wide')
24
+
25
+ # Session state for tracking training process
26
+ for key, value in {
27
+ "training": False,
28
+ "num_hidden_layers": 0,
29
+ "hidden_layer_neurons": [],
30
+ "prev_params": {},
31
+ }.items():
32
+ if key not in st.session_state:
33
+ st.session_state[key] = value
34
+
35
+ def reset_session():
36
+ st.session_state.clear()
37
+
38
+
39
+ st.title("Neural Network Playground")
40
+
41
+ # Sidebar for paramters
42
+ st.sidebar.title("Configure & Train Model")
43
+ problem_type = st.sidebar.selectbox("Problem Type", ["Classification",]) #"Regression"])
44
+ dataset_type = None
45
+ if problem_type == "Classification":
46
+ dataset_type = st.sidebar.selectbox("Select Dataset Type", ["Circle", "Gaussian", "Exclusive OR"])
47
+ # else:
48
+ # dataset_type = st.sidebar.selectbox("Select Dataset Type", ["Plane", "Gaussian Plane"])
49
+ col1, col2 = st.sidebar.columns(2)
50
+ with col1:
51
+ learning_rate = st.selectbox("Learning Rate", [0.00001,0.0001,0.001,0.01,0.03,0.1,0.3,1,3,10])
52
+ with col2:
53
+ activation_function = st.selectbox("Activation Function", ["ReLU", "Sigmoid", "Tanh"])
54
+
55
+ col1, col2 = st.sidebar.columns(2)
56
+ with col1:
57
+ regularization_type = st.selectbox("Regularization", ["None", "L1", "L2"])
58
+ with col2:
59
+ regularization_rate = st.selectbox("Regularization Rate", [0.0,0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1, 3, 10], disabled=(regularization_type == "None"))
60
+
61
+ train_to_test_ratio = st.sidebar.slider("Train-to-Test Ratio (%)", 10, 90, 20, 10) / 100
62
+ noise_level_slider = st.sidebar.slider("Noise Level", 0, 50, step=5)
63
+ batch_size = st.sidebar.slider("Batch Size", 1, 30, 10)
64
+
65
+ if st.sidebar.button("🔄 Reset Session"):
66
+ reset_session()
67
+ st.rerun()
68
+
69
+ # min noise
70
+ min_noise = 0.09
71
+
72
+ # Scaling the noise level to range [0.02, 0.2]
73
+ noise_level = min_noise + (noise_level_slider / 50) * (0.2 - min_noise)
74
+
75
+ # Store current parameter values in a dictionary
76
+ current_params = {
77
+ "dataset_type": dataset_type,
78
+ "learning_rate": learning_rate,
79
+ "regularization_type": regularization_type,
80
+ "regularization_rate": regularization_rate,
81
+ "activation_function": activation_function,
82
+ "train_to_test_ratio": train_to_test_ratio,
83
+ "batch_size": batch_size,
84
+ "noise_level": noise_level
85
+ }
86
+
87
+ gaussian_noise = 2.0 + ((noise_level_slider - 1) / 50) ** 2 * (10)
88
+
89
+ def make_xor(n_samples=250, noise=0):
90
+
91
+ # Base spread ensures some separation even when noise = 0
92
+ base_spread = 2.0
93
+ min_offset = 0.1 # Prevents tight clustering at corners
94
+
95
+ # Generate XOR quadrants
96
+ X1 = np.random.uniform(-base_spread, -min_offset, (n_samples, 2)) # Bottom-left
97
+ X2 = np.random.uniform(min_offset, base_spread, (n_samples, 2)) # Top-right
98
+ X3_x = np.random.uniform(-base_spread, -min_offset, (n_samples, 1)) # Top-left (x)
99
+ X3_y = np.random.uniform(min_offset, base_spread, (n_samples, 1)) # Top-left (y)
100
+ X3 = np.hstack([X3_x, X3_y])
101
+
102
+ X4_x = np.random.uniform(min_offset, base_spread, (n_samples, 1)) # Bottom-right (x)
103
+ X4_y = np.random.uniform(-base_spread, -min_offset, (n_samples, 1)) # Bottom-right (y)
104
+ X4 = np.hstack([X4_x, X4_y])
105
+
106
+ X = np.vstack([X1, X2, X3, X4])
107
+
108
+ # Apply smooth noise scaling
109
+ if noise > 0:
110
+ noise_scale = 0.05 + (noise / 100) # Small increase for gradual effect
111
+ X += np.random.randn(*X.shape) * noise_scale
112
+
113
+ # Define XOR labels: (1 if x and y have same sign, else 0)
114
+ y = np.logical_xor(X[:, 0] > 0, X[:, 1] > 0).astype(int)
115
+
116
+ return X, y
117
+
118
+ # Total dataset size
119
+ total_samples = 800
120
+
121
+ # Calculate training set size
122
+ train_size = int(total_samples * train_to_test_ratio)
123
+
124
+ def get_dataset(dataset_type, total_samples, noise_level, gaussian_noise, noise_level_slider):
125
+ # Dataset generators
126
+ dataset_generators = {
127
+ "Gaussian": lambda: make_blobs(n_samples=total_samples, centers=2, n_features=2, cluster_std=gaussian_noise, random_state=45),
128
+ "Circle": lambda: make_circles(n_samples=total_samples, shuffle=True, noise=noise_level, factor=0.2),
129
+ "Exclusive OR": lambda: make_xor(n_samples=total_samples, noise=noise_level_slider),
130
+ #"Spiral": lambda: make_spiral(n_samples=total_samples, noise=noise_level_slider),
131
+ }
132
+ return dataset_generators.get(dataset_type, lambda: (None, None))()
133
+ # Fetch dataset
134
+ if problem_type == "Classification":
135
+ fv, cv = get_dataset(dataset_type, total_samples, noise_level, gaussian_noise, noise_level_slider)
136
+
137
+
138
+ # Functions for modifying hidden layers
139
+ def add_layer():
140
+ if st.session_state.num_hidden_layers < 6:
141
+ st.session_state.num_hidden_layers += 1
142
+ st.session_state.hidden_layer_neurons.append(1)
143
+
144
+ def remove_layer():
145
+ if st.session_state.num_hidden_layers > 0 and st.session_state.hidden_layer_neurons:
146
+ st.session_state.num_hidden_layers -= 1
147
+ st.session_state.hidden_layer_neurons.pop()
148
+
149
+ # Functions for modifying neurons in each layer
150
+ def increase_neurons(layer_idx):
151
+ if st.session_state.hidden_layer_neurons[layer_idx] < 8:
152
+ st.session_state.hidden_layer_neurons[layer_idx] += 1
153
+
154
+ def decrease_neurons(layer_idx):
155
+ if st.session_state.hidden_layer_neurons[layer_idx] > 1:
156
+ st.session_state.hidden_layer_neurons[layer_idx] -= 1
157
+
158
+ col1, col2, col3 = st.columns([2, 2, 2])
159
+
160
+ with col1:
161
+ st.subheader("Select Input Features")
162
+
163
+ # Compute new features
164
+ std = StandardScaler()
165
+ X = std.fit_transform(fv)
166
+ x1, x2 = X[:, 0], X[:, 1]
167
+
168
+ # Update feature selection
169
+ available_features = ["X1", "X2"]
170
+
171
+ st.markdown("""
172
+ <style>
173
+ div[data-testid="stCheckbox"] {
174
+ background-color: #252830;
175
+ border-radius: 8px;
176
+ padding: 8px;
177
+ margin-bottom: 5px;
178
+ color: white;
179
+ }
180
+ div[data-testid="stCheckbox"] label {
181
+ font-size: 16px;
182
+ font-weight: bold;
183
+ color: white;
184
+ }
185
+ </style>
186
+ """, unsafe_allow_html=True)
187
+
188
+ selected_features = [feature for feature in available_features if st.checkbox(feature, value = st.session_state.get(feature, feature in ["X1", "X2"]), key=feature)]
189
+ st.session_state.selected_features = selected_features
190
+ num_inputs = len(selected_features)
191
+
192
+ # Map feature names to actual values
193
+ feature_mapping = {
194
+ "X1": x1,
195
+ "X2": x2,
196
+ }
197
+
198
+ if problem_type == 'Classification':
199
+ # Ensure a balanced split (Stratified Sampling)
200
+ x_train, x_test, y_train, y_test = train_test_split(
201
+ fv, cv,
202
+ test_size=1-train_to_test_ratio,
203
+ stratify=cv,
204
+ )
205
+ else:
206
+ # Ensure a balanced split
207
+ x_train, x_test, y_train, y_test = train_test_split(
208
+ fv, cv,
209
+ test_size=1-train_to_test_ratio
210
+ )
211
+ with col2:
212
+ # Visualize dataset
213
+ st.subheader("Dataset Preview")
214
+ fig, ax = plt.subplots(figsize=(3, 3))
215
+ scatter = ax.scatter(x_train[:, 0], x_train[:, 1], c=y_train, cmap="coolwarm", edgecolors="k", alpha=0.7)
216
+ ax.set_xticks([])
217
+ ax.set_yticks([])
218
+ ax.set_facecolor("#f0f0f0")
219
+
220
+ st.pyplot(fig)
221
+
222
+ num_outputs = 1
223
+ with col3:
224
+ st.subheader("Hidden Layers")
225
+ col1, col2 = st.columns([1, 1])
226
+ with col1:
227
+ st.button("➕ Add Layer", on_click=add_layer)
228
+ with col2:
229
+ st.button("➖ Remove Layer", on_click=remove_layer)
230
+
231
+ st.write("**Adjust Neurons in Each Layer:**")
232
+ for i in range(st.session_state.num_hidden_layers):
233
+ col1, col2, col3 = st.columns([1, 2, 1])
234
+ with col1:
235
+ st.button("➖", key=f"dec_neuron_{i}", on_click=decrease_neurons, args=(i,))
236
+ with col2:
237
+ st.markdown(f"**Layer {i+1}: {st.session_state.hidden_layer_neurons[i]} neurons**")
238
+ with col3:
239
+ st.button("➕", key=f"inc_neuron_{i}", on_click=increase_neurons, args=(i,))
240
+
241
+ # Stack selected features for training
242
+ selected_data = np.column_stack([feature_mapping[feature] for feature in selected_features])
243
+
244
+
245
+ # Function to draw the neural network visually
246
+ def draw_nn(selected_features, hidden_layer_neurons, num_outputs):
247
+ G = nx.DiGraph()
248
+
249
+ # Define layers dynamically
250
+ input_layer = selected_features # Match node names with feature names
251
+ hidden_layers = []
252
+ if st.session_state.num_hidden_layers > 0:
253
+ hidden_layers = [[f"hl{i+1}_{j+1}" for j in range(hidden_layer_neurons[i])] for i in range(st.session_state.num_hidden_layers)]
254
+ output_layer = ["y1"] # Single output neuron
255
+
256
+ layers = [input_layer] + hidden_layers + [output_layer]
257
+
258
+ # Add nodes and assign colors
259
+ node_colors = {}
260
+ input_color = "lightgreen"
261
+ hidden_color = "lightblue"
262
+ output_color = "salmon"
263
+
264
+ # Add nodes
265
+ # for layer_idx, layer in enumerate(layers):
266
+ # for node in layer:
267
+ # G.add_node(node, layer=layer_idx, edgecolors='black')
268
+ for layer_idx, layer in enumerate(layers):
269
+ for node in layer:
270
+ G.add_node(node, layer=layer_idx, edgecolors='black')
271
+ if layer_idx == 0:
272
+ node_colors[node] = input_color # Input layer
273
+ elif layer_idx == len(layers) - 1:
274
+ node_colors[node] = output_color # Output layer
275
+ else:
276
+ node_colors[node] = hidden_color # Hidden layers
277
+
278
+ # Add edges (fully connected between layers)
279
+ for i in range(len(layers) - 1):
280
+ for node1 in layers[i]:
281
+ for node2 in layers[i + 1]:
282
+ G.add_edge(node1, node2)
283
+
284
+ # Graph Layout
285
+ pos = nx.multipartite_layout(G, subset_key="layer")
286
+ fig, ax = plt.subplots(figsize=(12, 4))
287
+
288
+ # Style updates for TensorFlow Playground look
289
+ fig.patch.set_alpha(0)
290
+ ax.set_facecolor("#252830") # Dark background
291
+ ax.patch.set_alpha(1)
292
+
293
+ # Get color list
294
+ color_list = [node_colors[node] for node in G.nodes]
295
+
296
+ nx.draw(G, pos, with_labels=True, node_color=color_list, edge_color="white", edgecolors = "black",
297
+ node_size=800, font_size=7.5, ax=ax, width=0.4, font_color="black", font_weight="bold")
298
+
299
+ return fig
300
+
301
+ def create_ann_model(input_dim, hidden_layers, neurons_per_layer):
302
+ model = Sequential()
303
+ model.add(Input(shape=(input_dim,))) # Input layer
304
+
305
+ reg = None
306
+ if regularization_type == "L1":
307
+ reg = l1(regularization_rate)
308
+ elif regularization_type == "L2":
309
+ reg = l2(regularization_rate)
310
+
311
+ # Add hidden layers
312
+ for neurons in neurons_per_layer:
313
+ model.add(Dense(neurons, activation=activation_function.lower(), kernel_regularizer=reg))
314
+
315
+ # Output layer
316
+ model.add(Dense(1, activation='sigmoid'))
317
+
318
+ # Compile the model with explicit learning rate
319
+ optimizer = SGD(learning_rate=learning_rate)
320
+ model.compile(
321
+ optimizer=optimizer,
322
+ loss=BinaryCrossentropy(),
323
+ metrics=['accuracy']
324
+ )
325
+ return model
326
+
327
+ def plot_decision_boundary(model, x_train, y_train):
328
+ plt.figure(figsize=(6, 4))
329
+ plot_decision_regions(x_train, y_train, clf=model, legend=2)
330
+ #plt.title('Decision Boundary')
331
+ return plt
332
+
333
+ class LossPlotCallback(keras.callbacks.Callback):
334
+ def __init__(self, X, y, display_epochs=10):
335
+ super().__init__()
336
+ self.loss_df = pd.DataFrame(columns=["Epoch", "Train Loss", "Val Loss"])
337
+ #self.display_epochs = display_epochs
338
+ self.X = X
339
+ self.y = y
340
+ self.plot_placeholder = st.empty() # SINGLE container to update dynamically
341
+
342
+ def on_epoch_end(self, epoch, logs=None):
343
+ # Append new train and validation loss values
344
+ new_row = pd.DataFrame({
345
+ "Epoch": [epoch + 1],
346
+ "Train Loss": [logs['loss']],
347
+ "Val Loss": [logs['val_loss']]
348
+ })
349
+ self.loss_df = pd.concat([self.loss_df, new_row], ignore_index=True)
350
+
351
+ with self.plot_placeholder.container():
352
+ col1, col2 = st.columns([1, 1])
353
+
354
+ # Left Column: Decision Surface
355
+ with col1:
356
+ st.write("### Decision Boundary")
357
+ fig1 = plot_decision_boundary(ann_model, selected_data, cv)
358
+ st.pyplot(fig1, clear_figure=True)
359
+
360
+
361
+ # Right Column: Loss Plot
362
+ with col2:
363
+ st.write("### Training vs Validation Loss")
364
+ fig2, ax = plt.subplots(figsize=(6, 4), dpi=100)
365
+ ax.plot(self.loss_df["Epoch"], self.loss_df["Train Loss"], marker='o', markersize=1, linestyle='-', color='b', label="Train Loss")
366
+
367
+ if "Val Loss" in self.loss_df.columns and self.loss_df["Val Loss"].notna().any():
368
+ ax.plot(self.loss_df["Epoch"], self.loss_df["Val Loss"], marker='s',markersize=1, linestyle='--', color='r', label="Val Loss")
369
+
370
+ ax.set_xlabel("Epochs", fontsize=12, fontweight='bold')
371
+ ax.set_ylabel("Loss", fontsize=12, fontweight='bold')
372
+
373
+ #ax.set_title("Training vs Validation Loss", fontsize=14, fontweight='bold')
374
+
375
+ ax.legend(fontsize=10)
376
+
377
+ ax.grid(True, linestyle='--', alpha=0.6)
378
+ ax.spines['top'].set_visible(False)
379
+ ax.spines['right'].set_visible(False)
380
+
381
+ ax.set_xticks(range(1, len(self.loss_df) + 1),)
382
+ plt.xticks(rotation=45)
383
+ #ax.set_yticks(range(0, 1.0, 0.1))
384
+ st.pyplot(fig2, clear_figure=True)
385
+
386
+
387
+ if current_params != st.session_state.prev_params:
388
+ st.session_state.training = False # Stop training when a parameter changes
389
+ st.session_state.prev_params = current_params
390
+
391
+ # Start/Stop Buttons
392
+ col1, col2 = st.columns([1, 1])
393
+ with col1:
394
+ if st.button("▶️ Start Training"):
395
+ st.session_state.training = True
396
+ st.session_state.model_trained = False
397
+
398
+ with col2:
399
+ if st.button("⏹️ Stop Training"):
400
+ st.session_state.training = False
401
+
402
+ # Render the neural network visualization
403
+ st.write("### Logical Structure of the Neural Network")
404
+ st.pyplot(draw_nn(selected_features, st.session_state.hidden_layer_neurons, num_outputs))
405
+
406
+ # Train Model if Start is clicked
407
+ if st.session_state.training:
408
+ # Train the model and track loss in a DataFrame
409
+ ann_model = create_ann_model(
410
+ len(selected_features),
411
+ st.session_state.num_hidden_layers,
412
+ st.session_state.hidden_layer_neurons
413
+ )
414
+
415
+ st.session_state.model_trained = True
416
+
417
+ loss_plot_callback = LossPlotCallback(X=selected_data, y=cv)
418
+
419
+ # Capture model summary
420
+ model_summary = io.StringIO()
421
+ ann_model.summary(print_fn=lambda x: model_summary.write(x + "\n"))
422
+
423
+ # Display ANN model summary in Streamlit
424
+ st.subheader("Artificial Neural Network Model Summary")
425
+ st.code(model_summary.getvalue(), language="plaintext")
426
+
427
+ history = ann_model.fit(
428
+ x_train, y_train,
429
+ epochs=999999,
430
+ validation_data= (x_test, y_test),
431
+ batch_size=batch_size,
432
+ callbacks=[loss_plot_callback],
433
+ )
434
+
435
+ st.markdown(
436
+ """
437
+ <style>
438
+ .stApp {
439
+ background-image: url("https://cdn-uploads.huggingface.co/production/uploads/673f5e166c2774fcc8a82f0b/LFrNhoxTk1Bl1UitMviIm.jpeg");
440
+ background-size: cover;
441
+ background-position: center;
442
+ background-repeat: no-repeat;
443
+ background-attachment: fixed;
444
+ }
445
+
446
+ /* Semi-transparent overlay */
447
+ .stApp::before {
448
+ content: "";
449
+ position: absolute;
450
+ top: 0;
451
+ left: 0;
452
+ width: 100%;
453
+ height: 100%;
454
+ background: rgba(0, 0, 0, 0.4); /* 40% transparency */
455
+ z-index: -1;
456
+ }
457
+ </style>
458
+ """,
459
+ unsafe_allow_html=True)