NeonSamurai commited on
Commit
7ccfb9a
·
verified ·
1 Parent(s): bfa433b

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +458 -0
app.py ADDED
@@ -0,0 +1,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
+ 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
+ )