CosmickVisions commited on
Commit
029cbb4
·
verified ·
1 Parent(s): 2eae021

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +223 -382
app.py CHANGED
@@ -1,213 +1,61 @@
1
  import streamlit as st
2
- import pandas as pd
 
3
  import numpy as np
 
4
  import plotly.express as px
5
  import plotly.graph_objects as go
6
- from ydata_profiling import ProfileReport
7
- from streamlit_pandas_profiling import st_profile_report
8
- import os
9
- from datetime import datetime
10
- import re
11
- import tempfile
12
- from scipy import stats
13
- from sklearn.impute import SimpleImputer
14
- from sklearn.preprocessing import StandardScaler, LabelEncoder, OneHotEncoder
15
- from sklearn.decomposition import PCA
16
- import streamlit.components.v1 as components
17
- from io import StringIO
18
- import tensorflow as tf
19
- from tensorflow import keras
20
  from sklearn.model_selection import train_test_split, GridSearchCV
 
 
 
 
 
 
 
 
 
 
21
  from sklearn.cluster import KMeans, DBSCAN
22
  from sklearn.mixture import GaussianMixture
23
  from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
24
  from sklearn.linear_model import LogisticRegression, LinearRegression
25
  from xgboost import XGBClassifier, XGBRegressor
26
- from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_curve, auc, mean_squared_error, mean_absolute_error, r2_score
 
27
  import time
28
 
29
- # Custom CSS for modern styling (as before)
30
- st.markdown("""
31
- <style>
32
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap');
33
-
34
- /* Base styles */
35
- html {
36
- font-family: 'Inter', sans-serif;
37
- scroll-behavior: smooth;
38
- }
39
-
40
- /* Main container */
41
- .stApp {
42
- background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
43
- color: #2d3436;
44
- }
45
-
46
- /* Sidebar styling */
47
- .st-emotion-cache-6qob1r {
48
- background: linear-gradient(195deg, #2d3436 0%, #1a1e1f 100%) !important;
49
- border-right: 1px solid rgba(255,255,255,0.1) !important;
50
- }
51
-
52
- /* Data-Vision Pro title in sidebar (white) */
53
- .st-emotion-cache-6qob1r .stMarkdown h1 {
54
- color: white !important;
55
- }
56
-
57
- /* Navigation dropdown text black */
58
- .st-emotion-cache-6qob1r .stSelectbox label,
59
- .st-emotion-cache-6qob1r .stSelectbox div[data-baseweb="select"] > div {
60
- color: black !important;
61
- }
62
-
63
- /* Note text red */
64
- .st-emotion-cache-6qob1r .stMarkdown p:has(> em) {
65
- color: #ff4b4b !important;
66
- font-size: 0.9rem !important;
67
- margin: 0.25rem 0 !important;
68
- }
69
-
70
- /* Green links in sidebar */
71
- .st-emotion-cache-6qob1r .stMarkdown a {
72
- color: #4CAF50 !important;
73
- }
74
-
75
- /* Footer text white */
76
- .st-emotion-cache-6qob1r .stMarkdown p {
77
- color: white !important;
78
- font-size: 0.9rem !important;
79
- margin: 0.25rem 0 !important;
80
- }
81
-
82
- /* Footer name bold */
83
- .st-emotion-cache-6qob1r .stMarkdown p strong {
84
- font-weight: 600 !important;
85
- }
86
-
87
- /* Footer spacing and border */
88
- .st-emotion-cache-6qob1r .stMarkdown:has(> p > strong) {
89
- margin-top: 2rem !important;
90
- padding-top: 1rem !important;
91
- border-top: 1px solid rgba(255,255,255,0.1) !important;
92
- }
93
-
94
- /* Improved selectboxes */
95
- div[data-baseweb="select"] {
96
- border: 1px solid #ced4da;
97
- border-radius: 0.25rem;
98
- padding: 0.375rem 0.75rem;
99
- transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
100
- }
101
-
102
- div[data-baseweb="select"]:focus,
103
- div[data-baseweb="select"]:hover {
104
- border-color: #80bdff;
105
- box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
106
- }
107
-
108
- /* Metric cards */
109
- div[data-testid="metric-container"] {
110
- background-color: #fff;
111
- border-radius: 0.5rem;
112
- padding: 1rem;
113
- box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
114
- transition: all 0.3s ease;
115
- }
116
-
117
- div[data-testid="metric-container"]:hover {
118
- transform: translateY(-0.25rem);
119
- box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
120
- }
121
-
122
- /* Main content layout */
123
- .stApp {
124
- max-width: 1200px;
125
- margin: 0 auto;
126
- }
127
- </style>
128
- """, unsafe_allow_html=True)
129
-
130
- # Animated elements JavaScript (as before)
131
- components.html("""
132
- <script>
133
- document.querySelectorAll('[data-testid="metric-container"]').forEach(card => {
134
- card.style.transition = 'transform 0.3s ease, box-shadow 0.3s ease';
135
- card.addEventListener('mouseover', () => {
136
- card.style.transform = 'translateY(-4px)';
137
- card.style.boxShadow = '0 8px 16px rgba(0,0,0,0.1)';
138
- });
139
- card.addEventListener('mouseout', () => {
140
- card.style.transform = 'none';
141
- card.style.boxShadow = '0 4px 6px rgba(0,0,0,0.05)';
142
- });
143
- });
144
- </script>
145
- """, height=0)
146
-
147
- # Helper Functions (as before, but updated train_model and evaluate_model and added StreamlitCallback)
148
- def enhance_section_title(title):
149
- st.markdown(f"<h2 style='border-bottom: 2px solid #ccc; padding-bottom: 5px;'>{title}</h2>", unsafe_allow_html=True)
150
-
151
- def update_cleaned_data(df):
152
- st.session_state.cleaned_data = df
153
- if 'data_versions' not in st.session_state:
154
- st.session_state.data_versions = [st.session_state.raw_data.copy()]
155
- st.session_state.data_versions.append(df.copy())
156
- st.success("✅ Action completed successfully!")
157
- st.rerun()
158
-
159
- def generate_quality_report(df):
160
- report = {
161
- 'basic': {
162
- 'rows': df.shape[0],
163
- 'columns': df.shape[1],
164
- 'missing': df.isna().sum().sum(),
165
- 'duplicates': df.duplicated().sum()
166
- },
167
- 'columns': {}
168
- }
169
- for col in df.columns:
170
- col_report = {
171
- 'type': str(df[col].dtype),
172
- 'unique': df[col].nunique(),
173
- 'missing': df[col].isna().sum(),
174
- }
175
- if pd.api.types.is_numeric_dtype(df[col]):
176
- col_report.update({
177
- 'mean': df[col].mean(),
178
- 'std': df[col].std(),
179
- 'zeros': (df[col] == 0).sum()
180
- })
181
- report['columns'][col] = col_report
182
- return report
183
 
 
184
  def get_model_config(model_type, problem_type):
185
  configs = {
186
  "Random Forest": {
187
- "Regression": {"model_class": RandomForestRegressor, "params": {"n_estimators": 100},
188
  "grid_params": {"n_estimators": [50, 100, 200], "max_depth": [None, 10, 20]}},
189
- "Binary Classification": {"model_class": RandomForestClassifier, "params": {"n_estimators": 100},
190
  "grid_params": {"n_estimators": [50, 100, 200], "max_depth": [None, 10, 20]}},
191
- "Multi-Class": {"model_class": RandomForestClassifier, "params": {"n_estimators": 100},
192
  "grid_params": {"n_estimators": [50, 100, 200], "max_depth": [None, 10, 20]}}
193
  },
194
  "XGBoost": {
195
- "Regression": {"model_class": XGBRegressor, "params": {"n_estimators": 100},
196
  "grid_params": {"n_estimators": [50, 100, 200], "max_depth": [3, 5, 7], "learning_rate": [0.01, 0.1, 0.3]}},
197
- "Binary Classification": {"model_class": XGBClassifier, "params": {"n_estimators": 100},
198
  "grid_params": {"n_estimators": [50, 100, 200], "max_depth": [3, 5, 7], "learning_rate": [0.01, 0.1, 0.3]}},
199
- "Multi-Class": {"model_class": XGBClassifier, "params": {"n_estimators": 100},
200
  "grid_params": {"n_estimators": [50, 100, 200], "max_depth": [3, 5, 7], "learning_rate": [0.01, 0.1, 0.3]}}
201
  },
202
  "Logistic Regression": {
203
- "Binary Classification": {"model_class": LogisticRegression, "params": {"max_iter": 1000},
204
  "grid_params": {"C": [0.1, 1.0, 10.0], "solver": ["lbfgs", "liblinear"]}}
205
  },
206
  "Linear Regression": {
207
  "Regression": {"model_class": LinearRegression, "params": {}, "grid_params": {}}
208
  },
209
  "K-Means": {
210
- "Clustering": {"model_class": KMeans, "params": {"n_clusters": 3},
211
  "grid_params": {"n_clusters": [2, 3, 4, 5]}}
212
  },
213
  "DBSCAN": {
@@ -215,13 +63,14 @@ def get_model_config(model_type, problem_type):
215
  "grid_params": {"eps": [0.3, 0.5, 0.7], "min_samples": [3, 5, 10]}}
216
  },
217
  "Gaussian Mixture": {
218
- "Clustering": {"model_class": GaussianMixture, "params": {"n_components": 3},
219
  "grid_params": {"n_components": [2, 3, 4, 5]}}
220
  }
221
  }
222
  return configs.get(model_type, {}).get(problem_type, {"model_class": None, "params": {}, "grid_params": {}})
223
 
224
  def preprocess_data(X_train, X_test, numerical_features, categorical_features):
 
225
  numeric_transformer = Pipeline(steps=[
226
  ('imputer', SimpleImputer(strategy='mean')),
227
  ('scaler', StandardScaler())])
@@ -230,16 +79,22 @@ def preprocess_data(X_train, X_test, numerical_features, categorical_features):
230
  ('imputer', SimpleImputer(strategy='most_frequent')),
231
  ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False))])
232
 
 
233
  preprocessor = ColumnTransformer(
234
  transformers=[
235
  ('num', numeric_transformer, numerical_features),
236
  ('cat', categorical_transformer, categorical_features)],
237
- remainder='drop')
238
 
 
239
  X_train_processed = preprocessor.fit_transform(X_train)
 
 
240
  X_test_processed = preprocessor.transform(X_test)
241
 
 
242
  if categorical_features:
 
243
  onehot_encoder = preprocessor.named_transformers_['cat'].named_steps['onehot']
244
  categorical_feature_names = onehot_encoder.get_feature_names_out(categorical_features)
245
  feature_names = numerical_features + list(categorical_feature_names)
@@ -266,49 +121,41 @@ def build_neural_network(input_shape, output_units, problem_type, layers_config,
266
  metrics=["mae" if problem_type == "Regression" else "accuracy"])
267
  return model
268
 
269
- class StreamlitCallback(keras.callbacks.Callback):
270
- def __init__(self, chart_placeholder, metrics_placeholder):
271
- super().__init__()
272
- self.chart_placeholder = chart_placeholder
273
- self.metrics_placeholder = metrics_placeholder
274
- self.logs_data = []
275
-
276
- def on_epoch_end(self, epoch, logs=None):
277
- self.logs_data.append(logs)
278
- hist_df = pd.DataFrame(self.logs_data)
279
- with self.chart_placeholder.container(): # Use container for smoother updates
280
- fig_hist = px.line(hist_df, x=hist_df.index, y=hist_df.columns, labels={'index': 'Epoch', 'value': 'Metric'})
281
- st.plotly_chart(fig_hist, use_container_width=True)
282
-
283
- with self.metrics_placeholder.container():
284
- col_metrics = st.columns(len(logs))
285
- for idx, (metric_name, metric_value) in enumerate(logs.items()):
286
- col_metrics[idx].metric(metric_name.capitalize(), f"{metric_value:.4f}" if isinstance(metric_value, float) else metric_value)
287
-
288
-
289
- def train_model(model, X_train, y_train, X_test, y_test, epochs, batch_size, problem_type, callback, do_grid_search=False, params=None, grid_params=None): # Added callback
290
  start_time = time.time()
291
  history = None
292
-
293
  if isinstance(model, keras.Model):
294
- history = model.fit(
295
- X_train,
296
- y_train,
297
- epochs=epochs,
298
- batch_size=batch_size,
299
- validation_data=(X_test, y_test),
300
- verbose=0,
301
- callbacks=[callback] # Pass the callback here
302
- )
 
 
 
 
 
 
 
 
 
 
 
303
 
304
  else:
305
  if do_grid_search and grid_params:
306
- grid_search = GridSearchCV(model, grid_params, cv=3, n_jobs=-1)
307
  grid_search.fit(X_train, y_train)
308
  model = grid_search.best_estimator_
 
309
  else:
310
  model.set_params(**params)
311
  model.fit(X_train, y_train)
 
312
 
313
  training_time = time.time() - start_time
314
  return history, model, training_time
@@ -321,6 +168,7 @@ def evaluate_model(model, X_test, y_test, problem_type, le=None):
321
  metrics['mae'] = mean_absolute_error(y_test, y_pred)
322
  metrics['rmse'] = np.sqrt(metrics['mse'])
323
  metrics['r2'] = r2_score(y_test, y_pred)
 
324
  elif problem_type in ["Binary Classification", "Multi-Class"]:
325
  y_pred_classes = (y_pred > 0.5).astype(int).flatten() if problem_type == "Binary Classification" else np.argmax(y_pred, axis=1)
326
  y_test_classes = y_test if problem_type == "Binary Classification" else np.argmax(y_test, axis=1)
@@ -328,12 +176,10 @@ def evaluate_model(model, X_test, y_test, problem_type, le=None):
328
  metrics['precision'] = precision_score(y_test_classes, y_pred_classes, average='weighted', zero_division=0)
329
  metrics['recall'] = recall_score(y_test_classes, y_pred_classes, average='weighted', zero_division=0)
330
  metrics['f1'] = f1_score(y_test_classes, y_pred_classes, average='weighted', zero_division=0)
 
331
  elif problem_type == "Clustering":
332
  labels = model.labels_ if hasattr(model, 'labels_') else model.predict(X_test)
333
- metrics["n_clusters"] = len(np.unique(labels))
334
-
335
- return metrics, y_pred if problem_type != "Clustering" else labels
336
-
337
 
338
  def save_model(model, preprocessor, features, target, problem_type, filename="model.pkl"):
339
  model_data = {
@@ -356,148 +202,43 @@ def load_model(model_file):
356
  model_data['model'] = keras.models.load_model(model_data['model_path'])
357
  return model_data
358
 
359
-
360
- # Sidebar Navigation (as before)
361
  with st.sidebar:
362
- st.title("🔮 Data-Vision Pro")
363
- st.markdown("Your AI-powered data analysis suite.")
364
  st.markdown("---")
365
- app_mode = st.selectbox(
366
- "Navigation",
367
- ["Data Upload", "Data Cleaning", "EDA", "Model Training"], # Added Model Training to Navigation
368
- format_func=lambda x: f"📌 {x}"
369
- )
370
- if app_mode == "Data Upload":
371
- st.info("⬆️ Upload your CSV or XLSX dataset to begin.")
372
- elif app_mode == "Data Cleaning":
373
- st.info("🧹 Clean and preprocess your data using various tools.")
374
- elif app_mode == "EDA":
375
- st.info("🔍 Explore your data visually and statistically.")
376
- elif app_mode == "Model Training": # Info for Model Training
377
- st.info("🧠 Train and evaluate machine learning models.")
378
-
379
  st.markdown("---")
380
- st.markdown("**Note**: This app requires `ydata-profiling`, `tensorflow`, `scikit-learn`, etc.")
381
-
382
- # Download button (as before)
383
- if 'cleaned_data' in st.session_state:
384
- csv = st.session_state.cleaned_data.to_csv(index=False)
385
- st.download_button(
386
- label="Download Cleaned Data as CSV",
387
- data=csv,
388
- file_name='cleaned_data.csv',
389
- mime='text/csv',
390
- )
391
-
392
- st.markdown("Created by Calvin Allen-Crawford")
393
- st.markdown("v1.0 | © 2025")
394
-
395
- # Main App Pages (Data Upload, Data Cleaning, EDA - unchanged)
396
  if app_mode == "Data Upload":
397
- st.title("📤 Data Upload & Profiling")
398
- st.header("Upload Your Dataset")
399
- st.write("Supported formats: CSV, XLSX")
400
- uploaded_file = st.file_uploader("Choose a file", type=["csv", "xlsx"], key="file_uploader")
401
- if uploaded_file:
402
- st.session_state.pop('raw_data', None)
403
- st.session_state.pop('cleaned_data', None)
404
- st.session_state.pop('data_versions', None)
405
- st.session_state.pop('trained_model', None)
406
- st.session_state.pop('feature_names_model', None)
407
- st.session_state.pop('layers', None)
408
- st.session_state.pop('presets', None)
409
-
410
- try:
411
- if uploaded_file.name.endswith('.csv'):
412
- df = pd.read_csv(uploaded_file)
413
- else:
414
- df = pd.read_excel(uploaded_file)
415
- if df.empty:
416
- st.error("Uploaded file is empty. Please upload a valid dataset.")
417
- st.stop()
418
- st.session_state.raw_data = df
419
- if 'data_versions' not in st.session_state:
420
- st.session_state.data_versions = [df.copy()]
421
- col1, col2, col3 = st.columns(3)
422
- with col1: st.metric("Rows", df.shape[0])
423
- with col2: st.metric("Columns", df.shape[1])
424
- with col3: st.metric("Missing Values", df.isna().sum().sum())
425
- if st.checkbox("Show Data Preview"):
426
- st.dataframe(df.head(10), use_container_width=True)
427
- if st.button("Generate Full Profile Report"):
428
- with st.spinner("Generating report..."):
429
- pr = ProfileReport(df, explorative=True)
430
- st_profile_report(pr)
431
- st.success("✅ Data loaded and profiled successfully!")
432
- except Exception as e:
433
- st.error(f"An error occurred: {str(e)}")
434
-
435
- elif app_mode == "Data Cleaning":
436
- st.title("🧹 Smart Data Cleaning")
437
- st.header("Preprocess and Transform Your Data")
438
- if 'raw_data' not in st.session_state:
439
- st.warning("Please upload data first in the Data Upload section.")
440
- st.stop()
441
- if 'cleaned_data' not in st.session_state:
442
- st.session_state.cleaned_data = st.session_state.raw_data.copy()
443
- df = st.session_state.cleaned_data.copy()
444
 
445
- enhance_section_title("📊 Data Health Dashboard")
446
- with st.expander("Explore Data Health Metrics", expanded=True):
 
 
 
 
 
 
447
  col1, col2, col3 = st.columns(3)
448
- with col1: st.metric("Columns", len(df.columns))
449
- with col2: st.metric("Rows", len(df))
450
  with col3: st.metric("Missing Values", df.isna().sum().sum())
451
- if st.button("Generate Detailed Health Report"):
452
- with st.spinner("Generating report..."):
453
- profile = ProfileReport(df, minimal=True)
454
- st_profile_report(profile)
455
- if 'data_versions' in st.session_state and len(st.session_state.data_versions) > 1:
456
- if st.button("Undo Last Action"):
457
- st.session_state.data_versions.pop()
458
- st.session_state.cleaned_data = st.session_state.data_versions[-1].copy()
459
- st.rerun()
460
-
461
- with st.expander("🛠️ Data Cleaning Operations", expanded=True):
462
- # ... (Data Cleaning operations - unchanged) ...
463
- enhance_section_title("📊 Principal Component Analysis (PCA)")
464
- numerical_cols = df.select_dtypes(include=np.number).columns.tolist()
465
- if numerical_cols:
466
- pca_cols = st.multiselect("Select columns for PCA", numerical_cols, default=numerical_cols)
467
- if pca_cols:
468
- st.subheader("Covariance Matrix Heatmap")
469
- cov_matrix = df[pca_cols].cov()
470
- fig_cov = px.imshow(cov_matrix, labels=dict(x="Features", y="Features", color="Covariance"), color_continuous_scale='RdBu_r')
471
- st.plotly_chart(fig_cov)
472
- n_components = st.slider("Number of components", 1, min(len(pca_cols), 10), 2)
473
- if st.button("Apply PCA"):
474
- new_df = df.copy()
475
- scaler = StandardScaler()
476
- scaled_data = scaler.fit_transform(new_df[pca_cols])
477
- pca = PCA(n_components=n_components)
478
- pca_result = pca.fit_transform(scaled_data)
479
- pca_df = pd.DataFrame(pca_result, columns=[f'PC{i+1}' for i in range(n_components)])
480
- update_cleaned_data(pca_df.reset_index(drop=True))
481
- st.write("Explained Variance Ratio:", pca.explained_variance_ratio_)
482
- else:
483
- st.warning("No numerical columns available for PCA.")
484
-
485
- elif app_mode == "EDA":
486
- st.title("🔍 Interactive Data Explorer")
487
- # ... (EDA section - unchanged) ...
488
- if fig:
489
- fig.update_layout(template="plotly_white")
490
- st.plotly_chart(fig, use_container_width=True)
491
- else:
492
- st.error("Please provide required inputs for the selected plot type.")
493
 
494
  elif app_mode == "Model Training":
495
  st.title("🧠 Model Training")
496
- if 'cleaned_data' not in st.session_state:
497
- st.warning("Please upload and clean data first in the Data Upload and Data Cleaning sections.")
498
  st.stop()
499
 
500
- df = st.session_state.cleaned_data.copy()
501
  problem_type = st.selectbox("Problem Type", ["Regression", "Binary Classification", "Multi-Class", "Clustering"])
502
  features = st.multiselect("Select Features", df.columns)
503
  target = st.selectbox("Select Target", df.columns) if problem_type != "Clustering" else None
@@ -554,8 +295,9 @@ elif app_mode == "Model Training":
554
  param_name,
555
  min_value=float(min(param_values)),
556
  max_value=float(max(param_values)),
557
- value=float(param_values[1]))
558
 
 
559
  if param_name in {'n_estimators', 'n_clusters', 'min_samples',
560
  'n_components', 'max_depth'}:
561
  params[param_name] = int(slider_value)
@@ -574,7 +316,7 @@ elif app_mode == "Model Training":
574
  base_model = keras.models.load_model(uploaded_model) if uploaded_model else None
575
 
576
  if st.button("Train Model"):
577
- with st.spinner("Starting Training..."): # Initial spinner message
578
  X = df[features]
579
  y = df[target] if problem_type != "Clustering" else None
580
  X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) if problem_type != "Clustering" else (X, X.copy(), None, None)
@@ -591,41 +333,27 @@ elif app_mode == "Model Training":
591
  y_train = tf.keras.utils.to_categorical(y_train)
592
  y_test = tf.keras.utils.to_categorical(y_test)
593
 
 
 
594
  if model_type == "Neural Network":
595
  if not layers_config and not base_model:
596
  st.error("Please add layers or upload a pre-trained model.")
597
  st.stop()
598
- if problem_type == "Multi-Class":
599
- output_units = y_train.shape[1]
600
- else:
601
- output_units = 1
602
- model = build_neural_network(X_train_processed.shape[1:], output_units,
603
- problem_type, layers_config, "Adam", learning_rate)
604
-
605
- # Create placeholders for chart and metrics
606
- training_chart_placeholder = st.empty() # Use st.empty()
607
- training_metrics_placeholder = st.empty()
608
-
609
- # Create StreamlitCallback instance
610
- callback = StreamlitCallback(training_chart_placeholder, training_metrics_placeholder)
611
-
612
- history, model, training_time = train_model(
613
- model,
614
- X_train_processed,
615
- y_train,
616
- X_test_processed,
617
- y_test,
618
- epochs,
619
- batch_size,
620
- problem_type,
621
- callback # Pass the callback to train_model
622
- )
623
-
624
- else: # Non-NN Models - No real-time visualization
625
  config = get_model_config(model_type, problem_type)
626
  model = config['model_class'](**config['params'])
627
  history, model, training_time = train_model(model, X_train_processed, y_train, X_test_processed, y_test, epochs, batch_size, problem_type,
628
- None, params, config['grid_params']) # No callback for other models
 
 
 
 
 
 
629
 
630
  st.session_state.model = model
631
  st.session_state.preprocessor = preprocessor
@@ -639,9 +367,122 @@ elif app_mode == "Model Training":
639
  st.download_button("Download Model", f, file_name=filename)
640
  st.success(f"Model trained in {training_time:.2f}s and saved!")
641
 
642
- # Model Evaluation and Metrics Display (for all model types)
643
- st.subheader("Model Evaluation Metrics")
644
- metrics, _ = evaluate_model(model, X_test_processed, y_test, problem_type, le)
645
- metric_cols = st.columns(len(metrics))
646
- for idx, (metric_name, metric_value) in enumerate(metrics.items()):
647
- metric_cols[idx].metric(metric_name.capitalize(), f"{metric_value:.4f}" if isinstance(metric_value, float) else metric_value)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ import tensorflow as tf
3
+ from tensorflow import keras
4
  import numpy as np
5
+ import pandas as pd
6
  import plotly.express as px
7
  import plotly.graph_objects as go
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  from sklearn.model_selection import train_test_split, GridSearchCV
9
+ from sklearn.preprocessing import StandardScaler, LabelEncoder, OneHotEncoder
10
+ from sklearn.decomposition import PCA, TruncatedSVD
11
+ from sklearn.manifold import TSNE
12
+ import umap.umap_ as umap
13
+ import shap
14
+ import joblib
15
+ from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_curve, auc, mean_squared_error, mean_absolute_error, r2_score, classification_report
16
+ from sklearn.pipeline import Pipeline
17
+ from sklearn.compose import ColumnTransformer
18
+ from sklearn.impute import SimpleImputer
19
  from sklearn.cluster import KMeans, DBSCAN
20
  from sklearn.mixture import GaussianMixture
21
  from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
22
  from sklearn.linear_model import LogisticRegression, LinearRegression
23
  from xgboost import XGBClassifier, XGBRegressor
24
+ import matplotlib.pyplot as plt
25
+ from io import BytesIO
26
  import time
27
 
28
+ # Set page config
29
+ st.set_page_config(page_title="Neural-Vision Enhanced", layout="wide")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ # Helper Functions
32
  def get_model_config(model_type, problem_type):
33
  configs = {
34
  "Random Forest": {
35
+ "Regression": {"model_class": RandomForestRegressor, "params": {"n_estimators": 100, "random_state": 42},
36
  "grid_params": {"n_estimators": [50, 100, 200], "max_depth": [None, 10, 20]}},
37
+ "Binary Classification": {"model_class": RandomForestClassifier, "params": {"n_estimators": 100, "random_state": 42},
38
  "grid_params": {"n_estimators": [50, 100, 200], "max_depth": [None, 10, 20]}},
39
+ "Multi-Class": {"model_class": RandomForestClassifier, "params": {"n_estimators": 100, "random_state": 42},
40
  "grid_params": {"n_estimators": [50, 100, 200], "max_depth": [None, 10, 20]}}
41
  },
42
  "XGBoost": {
43
+ "Regression": {"model_class": XGBRegressor, "params": {"n_estimators": 100, "random_state": 42},
44
  "grid_params": {"n_estimators": [50, 100, 200], "max_depth": [3, 5, 7], "learning_rate": [0.01, 0.1, 0.3]}},
45
+ "Binary Classification": {"model_class": XGBClassifier, "params": {"n_estimators": 100, "random_state": 42, "use_label_encoder": False, "eval_metric": 'logloss'},
46
  "grid_params": {"n_estimators": [50, 100, 200], "max_depth": [3, 5, 7], "learning_rate": [0.01, 0.1, 0.3]}},
47
+ "Multi-Class": {"model_class": XGBClassifier, "params": {"n_estimators": 100, "random_state": 42, "use_label_encoder": False, "eval_metric": 'mlogloss'},
48
  "grid_params": {"n_estimators": [50, 100, 200], "max_depth": [3, 5, 7], "learning_rate": [0.01, 0.1, 0.3]}}
49
  },
50
  "Logistic Regression": {
51
+ "Binary Classification": {"model_class": LogisticRegression, "params": {"max_iter": 1000, "random_state": 42},
52
  "grid_params": {"C": [0.1, 1.0, 10.0], "solver": ["lbfgs", "liblinear"]}}
53
  },
54
  "Linear Regression": {
55
  "Regression": {"model_class": LinearRegression, "params": {}, "grid_params": {}}
56
  },
57
  "K-Means": {
58
+ "Clustering": {"model_class": KMeans, "params": {"n_clusters": 3, "random_state": 42},
59
  "grid_params": {"n_clusters": [2, 3, 4, 5]}}
60
  },
61
  "DBSCAN": {
 
63
  "grid_params": {"eps": [0.3, 0.5, 0.7], "min_samples": [3, 5, 10]}}
64
  },
65
  "Gaussian Mixture": {
66
+ "Clustering": {"model_class": GaussianMixture, "params": {"n_components": 3, "random_state": 42},
67
  "grid_params": {"n_components": [2, 3, 4, 5]}}
68
  }
69
  }
70
  return configs.get(model_type, {}).get(problem_type, {"model_class": None, "params": {}, "grid_params": {}})
71
 
72
  def preprocess_data(X_train, X_test, numerical_features, categorical_features):
73
+ # Define the numeric and categorical transformers
74
  numeric_transformer = Pipeline(steps=[
75
  ('imputer', SimpleImputer(strategy='mean')),
76
  ('scaler', StandardScaler())])
 
79
  ('imputer', SimpleImputer(strategy='most_frequent')),
80
  ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False))])
81
 
82
+ # Combine transformers into a ColumnTransformer
83
  preprocessor = ColumnTransformer(
84
  transformers=[
85
  ('num', numeric_transformer, numerical_features),
86
  ('cat', categorical_transformer, categorical_features)],
87
+ remainder='passthrough') # Handle unseen columns
88
 
89
+ # Fit and transform the training data
90
  X_train_processed = preprocessor.fit_transform(X_train)
91
+
92
+ # Transform the test data
93
  X_test_processed = preprocessor.transform(X_test)
94
 
95
+ # Get feature names after one-hot encoding
96
  if categorical_features:
97
+ # Access the fitted OneHotEncoder
98
  onehot_encoder = preprocessor.named_transformers_['cat'].named_steps['onehot']
99
  categorical_feature_names = onehot_encoder.get_feature_names_out(categorical_features)
100
  feature_names = numerical_features + list(categorical_feature_names)
 
121
  metrics=["mae" if problem_type == "Regression" else "accuracy"])
122
  return model
123
 
124
+ def train_model(model, X_train, y_train, X_test, y_test, epochs, batch_size, problem_type, do_grid_search=False, params=None, grid_params=None, training_placeholder=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  start_time = time.time()
126
  history = None
 
127
  if isinstance(model, keras.Model):
128
+ # Define a callback to update Streamlit during training
129
+ class StreamlitCallback(keras.callbacks.Callback):
130
+ def __init__(self, placeholder):
131
+ super().__init__()
132
+ self.placeholder = placeholder
133
+ self.epoch_data = []
134
+
135
+ def on_epoch_end(self, epoch, logs=None):
136
+ self.epoch_data.append(logs)
137
+ df = pd.DataFrame(self.epoch_data)
138
+ fig = px.line(df, x=df.index, y=['loss', 'val_loss'], labels={'index': 'Epoch', 'value': 'Loss'})
139
+ fig.add_trace(go.Scatter(x=df.index, y=df['accuracy'], mode='lines', name='accuracy'))
140
+ fig.add_trace(go.Scatter(x=df.index, y=df['val_accuracy'], mode='lines', name='val_accuracy'))
141
+
142
+ self.placeholder.plotly_chart(fig)
143
+
144
+ streamlit_callback = StreamlitCallback(training_placeholder)
145
+ history = model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size,
146
+ validation_data=(X_test, y_test), verbose=0,
147
+ callbacks=[streamlit_callback])
148
 
149
  else:
150
  if do_grid_search and grid_params:
151
+ grid_search = GridSearchCV(model, grid_params, cv=3, n_jobs=-1, scoring='accuracy' if problem_type in ["Binary Classification", "Multi-Class"] else 'neg_mean_squared_error') # Added scoring
152
  grid_search.fit(X_train, y_train)
153
  model = grid_search.best_estimator_
154
+ st.write("Best parameters found by Grid Search:", grid_search.best_params_) # Print best params
155
  else:
156
  model.set_params(**params)
157
  model.fit(X_train, y_train)
158
+ history = None # Reset to none here
159
 
160
  training_time = time.time() - start_time
161
  return history, model, training_time
 
168
  metrics['mae'] = mean_absolute_error(y_test, y_pred)
169
  metrics['rmse'] = np.sqrt(metrics['mse'])
170
  metrics['r2'] = r2_score(y_test, y_pred)
171
+ return metrics, y_pred.flatten()
172
  elif problem_type in ["Binary Classification", "Multi-Class"]:
173
  y_pred_classes = (y_pred > 0.5).astype(int).flatten() if problem_type == "Binary Classification" else np.argmax(y_pred, axis=1)
174
  y_test_classes = y_test if problem_type == "Binary Classification" else np.argmax(y_test, axis=1)
 
176
  metrics['precision'] = precision_score(y_test_classes, y_pred_classes, average='weighted', zero_division=0)
177
  metrics['recall'] = recall_score(y_test_classes, y_pred_classes, average='weighted', zero_division=0)
178
  metrics['f1'] = f1_score(y_test_classes, y_pred_classes, average='weighted', zero_division=0)
179
+ return metrics, y_pred_classes
180
  elif problem_type == "Clustering":
181
  labels = model.labels_ if hasattr(model, 'labels_') else model.predict(X_test)
182
+ return {"n_clusters": len(np.unique(labels))}, labels
 
 
 
183
 
184
  def save_model(model, preprocessor, features, target, problem_type, filename="model.pkl"):
185
  model_data = {
 
202
  model_data['model'] = keras.models.load_model(model_data['model_path'])
203
  return model_data
204
 
205
+ # Sidebar Navigation
 
206
  with st.sidebar:
207
+ st.title("🔮 Neural-Vision Enhanced")
208
+ st.markdown("Your AI-powered model toolbox.")
209
  st.markdown("---")
210
+ app_mode = st.selectbox("Navigation", ["Data Upload", "Model Training", "Validation & Exploration"])
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  st.markdown("---")
212
+ st.markdown("**Dependencies**: `tensorflow`, `shap`, `umap-learn`, `joblib`, `scikit-learn`, `plotly`, `xgboost`")
213
+ st.markdown("Created by Calvin Allen-Crawford | v1.2 | © 2025")
214
+
215
+ # Main App Sections
 
 
 
 
 
 
 
 
 
 
 
 
216
  if app_mode == "Data Upload":
217
+ st.title("📤 Data Upload")
218
+ col1, col2, col3 = st.columns([1, 2, 1])
219
+ with col2:
220
+ uploaded_file = st.file_uploader("Upload CSV Dataset", type=["csv"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
+ if uploaded_file:
223
+ df = pd.read_csv(uploaded_file)
224
+ st.session_state.df = df
225
+ st.write("---")
226
+ st.subheader("Dataset Preview")
227
+ st.dataframe(df.head(10))
228
+ st.write("---")
229
+ st.subheader("Statistics")
230
  col1, col2, col3 = st.columns(3)
231
+ with col1: st.metric("Rows", df.shape[0])
232
+ with col2: st.metric("Columns", df.shape[1])
233
  with col3: st.metric("Missing Values", df.isna().sum().sum())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
 
235
  elif app_mode == "Model Training":
236
  st.title("🧠 Model Training")
237
+ if 'df' not in st.session_state:
238
+ st.warning("Please upload a dataset first.")
239
  st.stop()
240
 
241
+ df = st.session_state.df
242
  problem_type = st.selectbox("Problem Type", ["Regression", "Binary Classification", "Multi-Class", "Clustering"])
243
  features = st.multiselect("Select Features", df.columns)
244
  target = st.selectbox("Select Target", df.columns) if problem_type != "Clustering" else None
 
295
  param_name,
296
  min_value=float(min(param_values)),
297
  max_value=float(max(param_values)),
298
+ value=float(param_values[1])
299
 
300
+ # CAST TO INT FOR INTEGER PARAMETERS
301
  if param_name in {'n_estimators', 'n_clusters', 'min_samples',
302
  'n_components', 'max_depth'}:
303
  params[param_name] = int(slider_value)
 
316
  base_model = keras.models.load_model(uploaded_model) if uploaded_model else None
317
 
318
  if st.button("Train Model"):
319
+ with st.spinner("Preparing data..."):
320
  X = df[features]
321
  y = df[target] if problem_type != "Clustering" else None
322
  X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) if problem_type != "Clustering" else (X, X.copy(), None, None)
 
333
  y_train = tf.keras.utils.to_categorical(y_train)
334
  y_test = tf.keras.utils.to_categorical(y_test)
335
 
336
+ with st.spinner("Training model..."):
337
+ training_placeholder = st.empty() # Placeholder for real-time training updates
338
  if model_type == "Neural Network":
339
  if not layers_config and not base_model:
340
  st.error("Please add layers or upload a pre-trained model.")
341
  st.stop()
342
+ model = base_model if base_model else build_neural_network(X_train_processed.shape[1:], y_train.shape[1] if problem_type == "Multi-Class" else 1,
343
+ problem_type, layers_config, "Adam", learning_rate)
344
+ history, model, training_time = train_model(model, X_train_processed, y_train, X_test_processed, y_test, epochs, batch_size, problem_type,
345
+ training_placeholder=training_placeholder)
346
+ else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  config = get_model_config(model_type, problem_type)
348
  model = config['model_class'](**config['params'])
349
  history, model, training_time = train_model(model, X_train_processed, y_train, X_test_processed, y_test, epochs, batch_size, problem_type,
350
+ do_grid_search, params, config['grid_params'])
351
+ st.subheader("Training Metrics")
352
+ if history:
353
+ fig = px.line(x=range(len(history.history['loss'])), y=history.history['loss'], labels={'x':'Epoch', 'y':'Loss'})
354
+ st.plotly_chart(fig)
355
+ else:
356
+ st.write("No history available for this model type.")
357
 
358
  st.session_state.model = model
359
  st.session_state.preprocessor = preprocessor
 
367
  st.download_button("Download Model", f, file_name=filename)
368
  st.success(f"Model trained in {training_time:.2f}s and saved!")
369
 
370
+ elif app_mode == "Validation & Exploration":
371
+ st.title("🔍 Validation & Exploration")
372
+ if 'model' not in st.session_state or 'df' not in st.session_state:
373
+ st.warning("Please upload a dataset and train a model first.")
374
+ st.stop()
375
+
376
+ df = st.session_state.df
377
+ model = st.session_state.model
378
+ preprocessor = st.session_state.preprocessor
379
+ features = st.session_state.features
380
+ target = st.session_state.target
381
+ problem_type = st.session_state.problem_type
382
+ le = st.session_state.le
383
+
384
+ X = df[features]
385
+ y = df[target] if problem_type != "Clustering" else None
386
+ X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) if problem_type != "Clustering" else (X, X.copy(), None, None)
387
+
388
+ # FIXED FEATURE SELECTION
389
+ numerical_features = X.select_dtypes(include=np.number).columns.tolist()
390
+ categorical_features = X.select_dtypes(exclude=np.number).columns.tolist()
391
+ X_train_processed, X_test_processed, feature_names, _ = preprocess_data(X_train, X_test, numerical_features, categorical_features)
392
+
393
+ if problem_type in ["Binary Classification", "Multi-Class"] and y is not None:
394
+ y_train = le.transform(y_train) if le else y_train
395
+ y_test = le.transform(y_test) if le else y_test
396
+ if problem_type == "Multi-Class":
397
+ y_train = tf.keras.utils.to_categorical(y_train)
398
+ y_test = tf.keras.utils.to_categorical(y_test)
399
+
400
+ # Validation
401
+ st.subheader("Model Validation")
402
+ metrics, y_pred = evaluate_model(model, X_test_processed, y_test, problem_type, le)
403
+ col1, col2 = st.columns(2)
404
+ with col1:
405
+ for metric, value in metrics.items():
406
+ st.metric(metric, f"{value:.4f}" if isinstance(value, float) else value)
407
+
408
+ with col2:
409
+ if problem_type == "Regression":
410
+ fig = px.scatter(x=y_test, y=y_pred, labels={"x": "Actual", "y": "Predicted"}, title="Predicted vs Actual")
411
+ st.plotly_chart(fig)
412
+ elif problem_type == "Binary Classification":
413
+ y_pred_proba = model.predict_proba(X_test_processed)[:, 1] if hasattr(model, 'predict_proba') else y_pred # Handle cases where predict_proba isn't available
414
+ fpr, tpr, _ = roc_curve(y_test, y_pred_proba)
415
+ roc_auc = auc(fpr, tpr)
416
+ fig = px.area(x=fpr, y=tpr, title=f"ROC Curve (AUC = {roc_auc:.2f})",
417
+ labels={"x": "False Positive Rate", "y": "True Positive Rate"})
418
+ st.plotly_chart(fig)
419
+ elif problem_type == "Multi-Class":
420
+ y_pred_classes = np.argmax(model.predict(X_test_processed), axis=1) if isinstance(model, keras.Model) else model.predict(X_test_processed)
421
+ y_test_classes = np.argmax(y_test, axis=1)
422
+ cm = np.zeros((y_train.shape[1], y_train.shape[1]))
423
+ for i, j in zip(y_test_classes, y_pred_classes):
424
+ cm[i, j] += 1
425
+ fig = px.imshow(cm, title="Confusion Matrix", labels={"x": "Predicted", "y": "Actual"})
426
+ st.plotly_chart(fig)
427
+
428
+ # Display Classification Report
429
+ report = classification_report(y_test_classes, y_pred_classes, target_names=le.classes_ if le else [str(i) for i in range(y_train.shape[1])], zero_division=0)
430
+ st.text("Classification Report:\n" + report)
431
+ elif problem_type == "Clustering":
432
+ labels = y_pred
433
+ fig = px.scatter(x=X_test_processed[:, 0], y=X_test_processed[:, 1], color=labels, title="Cluster Visualization")
434
+ st.plotly_chart(fig)
435
+
436
+ # Dimensionality Reduction
437
+ st.subheader("Dimensionality Reduction")
438
+ method = st.selectbox("Method", ["PCA", "SVD", "t-SNE", "UMAP"])
439
+ n_components = st.slider("Components", 2, min(X_train_processed.shape[1], 10), 2)
440
+
441
+ if method == "PCA":
442
+ reducer = PCA(n_components=n_components)
443
+ X_reduced = reducer.fit_transform(X_train_processed)
444
+ fig = px.bar(x=range(n_components), y=reducer.explained_variance_ratio_, title="Explained Variance Ratio")
445
+ st.plotly_chart(fig)
446
+ elif method == "SVD":
447
+ reducer = TruncatedSVD(n_components=n_components)
448
+ X_reduced = reducer.fit_transform(X_train_processed)
449
+ fig = px.bar(x=range(n_components), y=reducer.explained_variance_ratio_, title="Explained Variance Ratio")
450
+ st.plotly_chart(fig)
451
+ elif method == "t-SNE":
452
+ X_reduced = TSNE(n_components=n_components, random_state=42).fit_transform(X_train_processed)
453
+ elif method == "UMAP":
454
+ X_reduced = umap.UMAP(n_components=n_components, random_state=42).fit_transform(X_train_processed)
455
+
456
+ if n_components >= 2:
457
+ fig = px.scatter(x=X_reduced[:, 0], y=X_reduced[:, 1], color=y_train if problem_type != "Clustering" else y_pred,
458
+ title=f"{method} Visualization")
459
+ st.plotly_chart(fig)
460
+
461
+ # Interpretability
462
+ st.subheader("Interpretability")
463
+ try:
464
+ explainer = shap.KernelExplainer(model.predict, X_test_processed[:50]) if isinstance(model, keras.Model) else shap.Explainer(model, X_test_processed)
465
+ shap_values = explainer.shap_values(X_test_processed[:50])
466
+
467
+ if problem_type == "Regression":
468
+ shap_fig, ax = plt.subplots()
469
+ shap.summary_plot(shap_values, X_test_processed[:50], feature_names=feature_names, show=False)
470
+ st.pyplot(shap_fig)
471
+ elif problem_type in ["Binary Classification", "Multi-Class"]:
472
+ class_names = le.classes_ if le else [str(i) for i in range(y_train.shape[1])]
473
+ for i in range(len(class_names)):
474
+ shap_fig, ax = plt.subplots()
475
+ shap.summary_plot(shap_values[i], X_test_processed[:50], feature_names=feature_names, class_names=class_names, show=False)
476
+ st.pyplot(shap_fig)
477
+ else:
478
+ st.write("SHAP plots are not directly applicable to Clustering problems.")
479
+ except Exception as e:
480
+ st.error(f"Error generating SHAP plot: {e}")
481
+
482
+ # Custom CSS
483
+ st.markdown("""
484
+ <style>
485
+ .stButton>button {background-color: #4CAF50; color: white;}
486
+ h1, h2 {color: #1e3a8a;}
487
+ </style>
488
+ """, unsafe_allow_html=True)