Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,669 +5,448 @@ import seaborn as sns
|
|
| 5 |
import matplotlib.pyplot as plt
|
| 6 |
from statistics import mode, StatisticsError
|
| 7 |
|
| 8 |
-
#
|
| 9 |
-
from sklearn.model_selection import train_test_split
|
| 10 |
from sklearn.preprocessing import StandardScaler
|
| 11 |
from sklearn.pipeline import Pipeline
|
| 12 |
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score
|
| 13 |
-
from sklearn.ensemble import RandomForestClassifier
|
| 14 |
from sklearn.linear_model import LogisticRegression
|
| 15 |
from sklearn.svm import SVC
|
| 16 |
from sklearn.neural_network import MLPClassifier
|
| 17 |
|
| 18 |
-
#
|
| 19 |
import torch
|
| 20 |
import torch.nn as nn
|
| 21 |
import torch.nn.functional as F
|
| 22 |
|
| 23 |
-
# Transformers per
|
| 24 |
from transformers import pipeline
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
|
| 31 |
-
|
|
|
|
|
|
|
| 32 |
class DummyTabTransformerClassifier:
|
| 33 |
-
"""Finto modello: in realtà un MLP."""
|
| 34 |
def __init__(self, input_dim=8):
|
| 35 |
-
self.clf = MLPClassifier(hidden_layer_sizes=(
|
| 36 |
-
max_iter=100, random_state=42, alpha=0.01, learning_rate_init=0.01)
|
| 37 |
def fit(self, X, y):
|
| 38 |
-
self.clf.fit(X,
|
| 39 |
return self
|
| 40 |
def predict(self, X):
|
| 41 |
return self.clf.predict(X)
|
| 42 |
def predict_proba(self, X):
|
| 43 |
-
if hasattr(self.clf,
|
| 44 |
return self.clf.predict_proba(X)
|
| 45 |
else:
|
| 46 |
-
preds
|
| 47 |
return np.array([[1.0,0.0] if p==0 else [0.0,1.0] for p in preds])
|
| 48 |
|
| 49 |
-
|
| 50 |
-
""
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
def fit(self, X, y):
|
| 55 |
-
self.clf.fit(X, y)
|
| 56 |
-
return self
|
| 57 |
-
def predict(self, X):
|
| 58 |
-
return self.clf.predict(X)
|
| 59 |
-
def predict_proba(self, X):
|
| 60 |
-
if hasattr(self.clf, 'predict_proba'):
|
| 61 |
-
return self.clf.predict_proba(X)
|
| 62 |
-
else:
|
| 63 |
-
preds = self.clf.predict(X)
|
| 64 |
-
return np.array([[1.0,0.0] if p==0 else [0.0,1.0] for p in preds])
|
| 65 |
-
|
| 66 |
-
MODELS = {
|
| 67 |
-
"Random Forest": RandomForestClassifier(random_state=42, n_estimators=100, class_weight='balanced'),
|
| 68 |
-
"Gradient Boosting": GradientBoostingClassifier(random_state=42, n_estimators=100),
|
| 69 |
-
"Logistic Regression": LogisticRegression(random_state=42, max_iter=500, class_weight='balanced'),
|
| 70 |
-
"Support Vector Machine": SVC(probability=True, random_state=42, class_weight='balanced'),
|
| 71 |
-
"TabTransformer (Dummy)": DummyTabTransformerClassifier(),
|
| 72 |
-
"SAINT (Dummy)": DummySAINTClassifier()
|
| 73 |
}
|
| 74 |
|
| 75 |
-
|
|
|
|
|
|
|
| 76 |
class MiniVAE(nn.Module):
|
| 77 |
def __init__(self, input_dim=5, latent_dim=2):
|
| 78 |
super().__init__()
|
| 79 |
-
self.fc1 = nn.Linear(input_dim,
|
| 80 |
-
self.fc21
|
| 81 |
-
self.fc22
|
| 82 |
-
self.fc3 = nn.Linear(latent_dim,
|
| 83 |
-
self.fc4 = nn.Linear(32,
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
h = F.relu(self.fc1(x))
|
| 87 |
return self.fc21(h), self.fc22(h)
|
| 88 |
-
|
| 89 |
def reparameterize(self, mu, logvar):
|
| 90 |
-
std = torch.exp(0.5
|
| 91 |
eps = torch.randn_like(std)
|
| 92 |
return mu + eps*std
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
h = F.relu(self.fc3(z))
|
| 96 |
return self.fc4(h)
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
recon = self.decode(z)
|
| 102 |
return recon, mu, logvar
|
| 103 |
|
| 104 |
-
def vae_loss(recon_x,
|
| 105 |
-
|
| 106 |
-
kld = -0.5
|
| 107 |
-
return
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
shapes = np.random.choice(list(SHAPE_MAPPING.keys()), p=[0.4,0.3,0.2,0.1], size=n_samples)
|
| 130 |
-
weight = np.clip(np.random.normal(80,30,n_samples),10,250)
|
| 131 |
-
thickness= np.clip(np.random.normal(8,4,n_samples),0.5,30)
|
| 132 |
-
|
| 133 |
-
return pd.DataFrame({
|
| 134 |
-
'length': length,
|
| 135 |
-
'width': width,
|
| 136 |
-
'RUL': RUL,
|
| 137 |
-
'margin': margin,
|
| 138 |
-
'shape': shapes,
|
| 139 |
-
'weight': weight,
|
| 140 |
-
'thickness': thickness
|
| 141 |
-
})
|
| 142 |
-
|
| 143 |
-
############### dimension_match + assign_class ###############
|
| 144 |
-
def dimension_match(row, target_length, target_width, target_shape,
|
| 145 |
-
target_weight, target_thickness,
|
| 146 |
-
tol_len, tol_wid, tol_weight, tol_thickness):
|
| 147 |
-
cond_length = abs(row['length'] - target_length)<= tol_len
|
| 148 |
-
cond_width = abs(row['width'] - target_width) <= tol_wid
|
| 149 |
-
cond_shape = (row['shape']==target_shape)
|
| 150 |
-
cond_weight = abs(row['weight']-target_weight)<=tol_weight
|
| 151 |
-
cond_thick = abs(row['thickness']-target_thickness)<=tol_thickness
|
| 152 |
-
return 1 if (cond_length and cond_width and cond_shape and cond_weight and cond_thick) else 0
|
| 153 |
-
|
| 154 |
-
def assign_class(row, threshold_score=0.5, alpha=0.5, beta=0.5):
|
| 155 |
-
rul_norm = row['RUL']/1000.0
|
| 156 |
-
margin_norm = (row['margin']+200)/800.0
|
| 157 |
-
score = alpha*rul_norm + beta*margin_norm
|
| 158 |
-
if row['compat_dim']==1 and score>=threshold_score:
|
| 159 |
return "Riutilizzo Funzionale"
|
| 160 |
else:
|
| 161 |
return "Upcycling Creativo"
|
| 162 |
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
| 175 |
else:
|
| 176 |
-
|
| 177 |
-
if
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
except Exception as e:
|
| 186 |
-
st.error(f"Errore lettura CSV: {e}")
|
| 187 |
-
data=None
|
| 188 |
-
|
| 189 |
-
if data is not None:
|
| 190 |
-
with tab2:
|
| 191 |
-
st.subheader("Parametri per Compatibilità")
|
| 192 |
-
c1,c2=st.columns(2)
|
| 193 |
-
with c1:
|
| 194 |
-
t_len=st.number_input("Lunghezza target (mm)",50.0,250.0,100.0)
|
| 195 |
-
t_wid=st.number_input("Larghezza target (mm)",20.0,150.0,50.0)
|
| 196 |
-
t_shape=st.selectbox("Forma target", list(SHAPE_MAPPING.keys()))
|
| 197 |
-
with c2:
|
| 198 |
-
t_weight=st.number_input("Peso target (kg)",10.0,300.0,80.0)
|
| 199 |
-
t_thick=st.number_input("Spessore target (mm)",0.5,50.0,8.0)
|
| 200 |
-
|
| 201 |
-
st.markdown("**Tolleranze**")
|
| 202 |
-
col_tol1,col_tol2=st.columns(2)
|
| 203 |
-
with col_tol1:
|
| 204 |
-
tol_len=st.slider("Tol. lunghezza ±",0.0,30.0,5.0,step=0.5)
|
| 205 |
-
tol_wid=st.slider("Tol. larghezza ±",0.0,20.0,3.0,step=0.5)
|
| 206 |
-
with col_tol2:
|
| 207 |
-
tol_we=st.slider("Tol. peso ±",0.0,50.0,10.0,step=1.0)
|
| 208 |
-
tol_th=st.slider("Tol. spessore ±",0.0,5.0,1.0,step=0.1)
|
| 209 |
-
|
| 210 |
-
st.markdown("**Score RUL & Margin**")
|
| 211 |
-
thr_sc=st.slider("Soglia score",0.0,1.0,0.5,step=0.05)
|
| 212 |
-
alpha=st.slider("Peso RUL(α)",0.0,1.0,0.5,step=0.05)
|
| 213 |
-
beta =st.slider("Peso Margin(β)",0.0,1.0,0.5,step=0.05)
|
| 214 |
-
|
| 215 |
-
# shape_code
|
| 216 |
-
data['shape_code']=data['shape'].map(SHAPE_MAPPING).fillna(-1).astype(int)
|
| 217 |
-
data['compat_dim']= data.apply(lambda r: dimension_match(r,
|
| 218 |
-
target_length=t_len,
|
| 219 |
-
target_width=t_wid,
|
| 220 |
-
target_shape=t_shape,
|
| 221 |
-
target_weight=t_weight,
|
| 222 |
-
target_thickness=t_thick,
|
| 223 |
-
tol_len=tol_len,
|
| 224 |
-
tol_wid=tol_wid,
|
| 225 |
-
tol_weight=tol_we,
|
| 226 |
-
tol_thickness=tol_th), axis=1)
|
| 227 |
-
|
| 228 |
-
data['Target'] = data.apply(lambda r: assign_class(r,
|
| 229 |
-
threshold_score=thr_sc,
|
| 230 |
-
alpha=alpha,beta=beta), axis=1)
|
| 231 |
-
|
| 232 |
-
st.session_state.target_params={
|
| 233 |
-
"target_length": t_len, "target_width": t_wid,
|
| 234 |
-
"target_shape": t_shape, "target_weight": t_weight,
|
| 235 |
-
"target_thickness": t_thick,
|
| 236 |
-
"tol_len": tol_len, "tol_wid": tol_wid,
|
| 237 |
-
"tol_weight": tol_we, "tol_thickness": tol_th
|
| 238 |
-
}
|
| 239 |
-
st.session_state.score_params={
|
| 240 |
-
"threshold_score": thr_sc,
|
| 241 |
-
"alpha": alpha,
|
| 242 |
-
"beta": beta
|
| 243 |
-
}
|
| 244 |
-
|
| 245 |
-
st.dataframe(data.head(10))
|
| 246 |
-
st.write("Distribuzione classi:", data["Target"].value_counts())
|
| 247 |
-
|
| 248 |
-
numeric_cols=data.select_dtypes(include=np.number)
|
| 249 |
-
if not numeric_cols.empty:
|
| 250 |
-
fig,ax=plt.subplots()
|
| 251 |
-
sns.heatmap(numeric_cols.corr(), annot=True, cmap='viridis', fmt=".2f", ax=ax)
|
| 252 |
-
st.pyplot(fig)
|
| 253 |
-
|
| 254 |
-
st.session_state.data=data
|
| 255 |
-
csv_proc=data.to_csv(index=False).encode('utf-8')
|
| 256 |
-
st.download_button("Scarica Dataset Elaborato", csv_proc, "dataset_processed.csv")
|
| 257 |
-
|
| 258 |
-
############### 2) PHASE: TRAIN MODELLI ML ############
|
| 259 |
-
def train_models(data):
|
| 260 |
-
st.header("🤖 2. Addestramento ML (Riutilizzo vs Upcycling)")
|
| 261 |
if data is None:
|
| 262 |
-
st.
|
| 263 |
-
return
|
| 264 |
-
if 'Target' not in data.columns:
|
| 265 |
-
st.error("Colonna 'Target' mancante. Rivedi la Fase 1.")
|
| 266 |
return
|
| 267 |
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
return
|
| 272 |
|
| 273 |
-
|
| 274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
if len(y.unique())<2:
|
| 276 |
st.error("Il dataset ha una sola classe. Non si può addestrare.")
|
| 277 |
return
|
| 278 |
-
|
|
|
|
| 279 |
st.write(f"Train={len(X_train)}, Test={len(X_test)}")
|
| 280 |
|
| 281 |
-
|
| 282 |
-
|
| 283 |
results=[]
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
st.subheader(f"Modello: {name}")
|
| 287 |
from sklearn.pipeline import Pipeline
|
| 288 |
-
pipe=Pipeline([
|
| 289 |
-
("scaler",StandardScaler()),
|
| 290 |
("clf",model)
|
| 291 |
])
|
| 292 |
try:
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
st.write(f"Migliori parametri: {grid.best_params_}")
|
| 304 |
-
y_pred=best_est.predict(X_test)
|
| 305 |
-
pipe_to_use=best_est
|
| 306 |
-
else:
|
| 307 |
-
pipe.fit(X_train,y_train)
|
| 308 |
-
y_pred=pipe.predict(X_test)
|
| 309 |
-
pipe_to_use=pipe
|
| 310 |
-
|
| 311 |
-
acc=accuracy_score(y_test,y_pred)
|
| 312 |
-
f1=f1_score(y_test,y_pred,average='weighted')
|
| 313 |
-
results.append({"Modello":name, "Accuracy":acc, "F1 Score":f1})
|
| 314 |
-
trained_pipelines[name]=pipe_to_use
|
| 315 |
-
|
| 316 |
-
cm=confusion_matrix(y_test,y_pred)
|
| 317 |
-
fig,ax=plt.subplots()
|
| 318 |
-
sns.heatmap(cm,annot=True,fmt='d',cmap="Greens",ax=ax)
|
| 319 |
plt.xlabel("Pred")
|
| 320 |
plt.ylabel("True")
|
| 321 |
st.pyplot(fig)
|
|
|
|
| 322 |
st.metric("Accuracy",f"{acc:.3f}")
|
| 323 |
st.metric("F1 Score",f"{f1:.3f}")
|
| 324 |
-
|
| 325 |
except Exception as e:
|
| 326 |
-
st.error(f"Errore addestramento {
|
| 327 |
|
| 328 |
if results:
|
| 329 |
-
|
| 330 |
-
st.dataframe(
|
| 331 |
-
st.session_state
|
| 332 |
-
st.session_state
|
| 333 |
else:
|
| 334 |
st.error("Nessun modello addestrato con successo.")
|
| 335 |
-
st.session_state
|
| 336 |
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
|
|
|
|
|
|
| 340 |
|
| 341 |
-
|
| 342 |
-
st.error("Prima addestra i modelli ML (Fase 2).")
|
| 343 |
-
return
|
| 344 |
if data is None:
|
| 345 |
-
st.error("
|
| 346 |
return
|
| 347 |
-
|
| 348 |
-
# Usiamo mediane del dataset per default
|
| 349 |
-
data_stats=data
|
| 350 |
-
|
| 351 |
-
with st.form("inference_form"):
|
| 352 |
-
st.subheader("Inserisci Dati EoL")
|
| 353 |
-
c1,c2,c3=st.columns(3)
|
| 354 |
-
with c1:
|
| 355 |
-
length=st.number_input("Lunghezza (mm)",0.0,300.0,float(data_stats['length'].median()),step=1.0)
|
| 356 |
-
width= st.number_input("Larghezza (mm)",0.0,200.0,float(data_stats['width'].median()),step=1.0)
|
| 357 |
-
shape_name = st.selectbox("Forma", list(SHAPE_MAPPING.keys()))
|
| 358 |
-
with c2:
|
| 359 |
-
weight= st.number_input("Peso (kg)",0.0,300.0,float(data_stats['weight'].median()),step=1.0)
|
| 360 |
-
thickness= st.number_input("Spessore (mm)",0.0,50.0,float(data_stats['thickness'].median()),step=0.5)
|
| 361 |
-
RUL= st.number_input("RUL (0-1000)",0,1000,int(data_stats['RUL'].median()))
|
| 362 |
-
with c3:
|
| 363 |
-
val_merc= st.number_input("Valore Mercato (€)",0.0,1e5,float(data_stats['margin'].median()+200),step=10.0)
|
| 364 |
-
costo_rip= st.number_input("Costo Riparazione (€)",0.0,1e5,50.0,step=10.0)
|
| 365 |
-
|
| 366 |
-
sub=st.form_submit_button("Predizione Step 1")
|
| 367 |
-
|
| 368 |
-
if sub:
|
| 369 |
-
margin= val_merc - costo_rip
|
| 370 |
-
shape_code = SHAPE_MAPPING.get(shape_name,-1)
|
| 371 |
-
|
| 372 |
-
input_dict={
|
| 373 |
-
"length":length,
|
| 374 |
-
"width":width,
|
| 375 |
-
"shape":shape_name,
|
| 376 |
-
"weight":weight,
|
| 377 |
-
"thickness":thickness,
|
| 378 |
-
"RUL":RUL,
|
| 379 |
-
"margin":margin
|
| 380 |
-
}
|
| 381 |
-
temp_df=pd.DataFrame([input_dict])
|
| 382 |
-
|
| 383 |
-
# compat_dim
|
| 384 |
-
if 'target_params' not in st.session_state:
|
| 385 |
-
st.error("Parametri target non definiti. Fase 1 mancante.")
|
| 386 |
-
return
|
| 387 |
-
param_t=st.session_state.target_params
|
| 388 |
-
temp_df['compat_dim'] = temp_df.apply(lambda r:
|
| 389 |
-
dimension_match(r, **param_t), axis=1)
|
| 390 |
-
|
| 391 |
-
# shape_code
|
| 392 |
-
temp_df['shape_code']= shape_code
|
| 393 |
-
# Manteniamo solo le col ML
|
| 394 |
-
try:
|
| 395 |
-
X_inference=temp_df[ML_FEATURES_STEP1]
|
| 396 |
-
except KeyError as e:
|
| 397 |
-
st.error(f"Mancano feature: {e}")
|
| 398 |
-
return
|
| 399 |
-
|
| 400 |
-
# Eseguiamo predizione con i modelli
|
| 401 |
-
preds=[]
|
| 402 |
-
details=[]
|
| 403 |
-
for name,pipe in trained_pipelines.items():
|
| 404 |
-
try:
|
| 405 |
-
p_num=pipe.predict(X_inference)[0]
|
| 406 |
-
proba=pipe.predict_proba(X_inference)[0]
|
| 407 |
-
details.append({
|
| 408 |
-
"Modello":name,
|
| 409 |
-
"Pred(0=Riu,1=Upc)": p_num,
|
| 410 |
-
"Prob_Riutilizzo": proba[0],
|
| 411 |
-
"Prob_Upcycling": proba[1]
|
| 412 |
-
})
|
| 413 |
-
preds.append(p_num)
|
| 414 |
-
except Exception as e:
|
| 415 |
-
st.error(f"Errore predizione {name}: {e}")
|
| 416 |
-
|
| 417 |
-
if not preds:
|
| 418 |
-
st.error("Nessuna predizione valida.")
|
| 419 |
-
return
|
| 420 |
-
# Aggrega con mode
|
| 421 |
-
from statistics import mode, StatisticsError
|
| 422 |
-
try:
|
| 423 |
-
final_pred=mode(preds)
|
| 424 |
-
except StatisticsError:
|
| 425 |
-
# Se c'è tie, guardiamo la media upcycling
|
| 426 |
-
avg_upc= np.mean([d["Prob_Upcycling"] for d in details])
|
| 427 |
-
final_pred=1 if avg_upc>=0.5 else 0
|
| 428 |
-
|
| 429 |
-
final_label="Riutilizzo Funzionale" if final_pred==0 else "Upcycling Creativo"
|
| 430 |
-
st.subheader("Risultato Aggregato")
|
| 431 |
-
st.metric("Classe", final_label)
|
| 432 |
-
|
| 433 |
-
with st.expander("Dettagli singoli modelli"):
|
| 434 |
-
df_det=pd.DataFrame(details)
|
| 435 |
-
df_det["Prob_Riutilizzo"]= df_det["Prob_Riutilizzo"].apply(lambda x:f"{x:.1%}")
|
| 436 |
-
df_det["Prob_Upcycling"]= df_det["Prob_Upcycling"].apply(lambda x:f"{x:.1%}")
|
| 437 |
-
st.dataframe(df_det)
|
| 438 |
-
|
| 439 |
-
# Se Upcycling Creativo => Step 2 (VAE + GenAI)
|
| 440 |
-
if final_label=="Upcycling Creativo":
|
| 441 |
-
st.markdown("---")
|
| 442 |
-
st.subheader("Fase 2: Generazione con VAE + GenAI Testuale")
|
| 443 |
-
|
| 444 |
-
if not st.session_state.get("vae_trained_on_eol",False):
|
| 445 |
-
st.error("VAE non addestrato. Vai a '🧬 Training VAE (Step 2)'.")
|
| 446 |
-
return
|
| 447 |
-
vae_model= st.session_state.get("vae",None)
|
| 448 |
-
vae_scaler=st.session_state.get("vae_scaler",None)
|
| 449 |
-
if vae_model is None or vae_scaler is None:
|
| 450 |
-
st.error("VAE o scaler mancanti in session.")
|
| 451 |
-
return
|
| 452 |
-
|
| 453 |
-
n_ideas=st.number_input("Quante idee generare col VAE?",1,10,3)
|
| 454 |
-
if st.button("Genera Configurazioni + Testo Upcycling"):
|
| 455 |
-
vae_model.eval()
|
| 456 |
-
with torch.no_grad():
|
| 457 |
-
lat_dim= vae_model.fc21.out_features
|
| 458 |
-
z=torch.randn(n_ideas, lat_dim)
|
| 459 |
-
recon= vae_model.decode(z)
|
| 460 |
-
|
| 461 |
-
arr=recon.numpy()
|
| 462 |
-
try:
|
| 463 |
-
arr_inv= vae_scaler.inverse_transform(arr)
|
| 464 |
-
feat_names= vae_scaler.feature_names_in_
|
| 465 |
-
df_gen= pd.DataFrame(arr_inv, columns=feat_names)
|
| 466 |
-
|
| 467 |
-
# Riconverti shape_code -> shape
|
| 468 |
-
if 'shape_code' in df_gen.columns:
|
| 469 |
-
df_gen['shape_code']= df_gen['shape_code'].round().astype(int)
|
| 470 |
-
inv_map={v:k for k,v in SHAPE_MAPPING.items()}
|
| 471 |
-
df_gen['shape']= df_gen['shape_code'].map(inv_map).fillna('unknown')
|
| 472 |
-
|
| 473 |
-
st.write("**Configurazioni generate (VAE)**")
|
| 474 |
-
st.dataframe(df_gen.round(2))
|
| 475 |
-
|
| 476 |
-
# --- Integrazione GenAI testuale con Transformers ---
|
| 477 |
-
st.markdown("#### Suggerimenti testuali per ciascuna configurazione")
|
| 478 |
-
|
| 479 |
-
# Carichiamo pipeline testuale (distilgpt2, ad es.)
|
| 480 |
-
# Se su HF Spaces serve un modello leggero
|
| 481 |
-
text_generator = pipeline(
|
| 482 |
-
"text-generation",
|
| 483 |
-
model="distilgpt2",
|
| 484 |
-
device=0 if torch.cuda.is_available() else -1
|
| 485 |
-
)
|
| 486 |
-
|
| 487 |
-
def gen_upcycle_text(row):
|
| 488 |
-
shape = row.get("shape","unknown")
|
| 489 |
-
thick = row.get("thickness",0.0)
|
| 490 |
-
wei = row.get("weight",0.0)
|
| 491 |
-
prompt= (
|
| 492 |
-
f"Ho un componente EoL con forma {shape}, spessore {thick:.1f} mm, peso {wei:.1f} kg.\n"
|
| 493 |
-
"Dammi un'idea creativa di upcycling (in italiano) con passaggi principali:"
|
| 494 |
-
)
|
| 495 |
-
result= text_generator(prompt, max_new_tokens=50, do_sample=True, top_k=50)
|
| 496 |
-
return result[0]["generated_text"]
|
| 497 |
-
|
| 498 |
-
ideas_text=[]
|
| 499 |
-
for i, r in df_gen.iterrows():
|
| 500 |
-
text_sugg = gen_upcycle_text(r)
|
| 501 |
-
ideas_text.append(text_sugg)
|
| 502 |
-
|
| 503 |
-
for i, r in df_gen.iterrows():
|
| 504 |
-
st.write(f"**Idea {i+1}** - shape={r['shape']}, thickness={r['thickness']:.1f}, weight={r['weight']:.1f}")
|
| 505 |
-
st.info(ideas_text[i])
|
| 506 |
-
st.markdown("---")
|
| 507 |
-
|
| 508 |
-
except Exception as e:
|
| 509 |
-
st.error(f"Errore decoding VAE: {e}")
|
| 510 |
-
else:
|
| 511 |
-
st.success("Predetto: Riutilizzo Funzionale. Nessun passaggio generativo richiesto.")
|
| 512 |
-
|
| 513 |
-
############### 4) PHASE: TRAINING VAE ############
|
| 514 |
-
def vae_training_phase():
|
| 515 |
-
st.header("🧬 Training VAE (Step 2)")
|
| 516 |
-
if 'data' not in st.session_state or st.session_state['data'] is None:
|
| 517 |
-
st.error("Nessun dataset in session. Torna a Fase 1.")
|
| 518 |
-
return
|
| 519 |
-
data=st.session_state['data']
|
| 520 |
-
|
| 521 |
-
feats=[f for f in VAE_FEATURES_STEP2 if f in data.columns]
|
| 522 |
if not feats:
|
| 523 |
-
st.error(f"
|
| 524 |
return
|
| 525 |
-
st.write(
|
| 526 |
|
| 527 |
-
lat_dim= st.slider("
|
| 528 |
-
ep= st.number_input("
|
| 529 |
-
lr= st.number_input("Learning Rate
|
| 530 |
-
bs= st.selectbox("Batch
|
| 531 |
|
| 532 |
-
if not st.session_state.get("
|
| 533 |
-
st.warning("VAE non addestrato.")
|
| 534 |
-
if st.button("
|
| 535 |
-
#
|
| 536 |
st.session_state["vae"]= MiniVAE(input_dim=len(feats), latent_dim=lat_dim)
|
| 537 |
-
st.session_state["vae_trained_on_eol"]=False
|
| 538 |
-
|
| 539 |
from sklearn.preprocessing import StandardScaler
|
| 540 |
X_vae= data[feats].copy()
|
| 541 |
-
# Riempi NaN
|
| 542 |
for c in X_vae.columns:
|
| 543 |
if X_vae[c].isnull().any():
|
| 544 |
-
X_vae[c].fillna(X_vae[c].median(),
|
| 545 |
-
|
| 546 |
-
# Scalatura
|
| 547 |
scaler= StandardScaler()
|
| 548 |
-
|
| 549 |
st.session_state["vae_scaler"]= scaler
|
| 550 |
|
| 551 |
-
dataset=torch.utils.data.TensorDataset(torch.tensor(
|
| 552 |
loader= torch.utils.data.DataLoader(dataset,batch_size=bs,shuffle=True)
|
| 553 |
-
vae=st.session_state["vae"]
|
| 554 |
-
|
| 555 |
-
|
| 556 |
losses=[]
|
| 557 |
vae.train()
|
| 558 |
for epoch in range(int(ep)):
|
| 559 |
-
|
| 560 |
for (batch,) in loader:
|
| 561 |
-
|
| 562 |
recon, mu, logvar= vae(batch)
|
| 563 |
-
loss= vae_loss(recon,
|
| 564 |
loss.backward()
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
losses.append(
|
| 569 |
st.progress((epoch+1)/ep)
|
| 570 |
-
st.
|
| 571 |
-
st.success(f"VAE addestrato. Ultimo Loss ~ {avg_loss:.2f}")
|
| 572 |
st.line_chart(losses)
|
|
|
|
| 573 |
else:
|
| 574 |
-
st.success("VAE già addestrato. Se vuoi
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 578 |
|
| 579 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 580 |
def show_dashboard():
|
| 581 |
-
st.header("
|
| 582 |
-
data= st.session_state.get(
|
| 583 |
if data is None:
|
| 584 |
st.error("Nessun dataset.")
|
| 585 |
return
|
|
|
|
| 586 |
|
| 587 |
-
st.
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
if 'train_results' in st.session_state and st.session_state['train_results'] is not None:
|
| 591 |
-
df_res=st.session_state['train_results']
|
| 592 |
-
st.subheader("Risultati modelli ML")
|
| 593 |
-
st.dataframe(df_res)
|
| 594 |
else:
|
| 595 |
-
st.info("Nessun risultato
|
| 596 |
|
| 597 |
-
st.
|
| 598 |
-
|
| 599 |
-
st.success("VAE addestrato. Pronto per generare idee di upcycling.")
|
| 600 |
else:
|
| 601 |
-
st.warning("VAE non addestrato")
|
| 602 |
|
| 603 |
-
|
|
|
|
|
|
|
| 604 |
def show_help():
|
| 605 |
-
st.header("ℹ️ Guida all'Uso")
|
| 606 |
st.markdown("""
|
| 607 |
-
**Flusso
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
- Generi/carichi dati su dimensioni, shape, RUL, margin, ecc.
|
| 611 |
-
- Definisci parametri e tolleranze per la compatibilità, assegni classi ("Riutilizzo Funzionale" vs "Upcycling Creativo").
|
| 612 |
-
|
| 613 |
-
2. **Fase 2 (Addestramento ML)**:
|
| 614 |
-
- Alleni vari modelli (RF, SVM...) per predire la classe su nuovi EoL.
|
| 615 |
|
| 616 |
-
|
| 617 |
-
-
|
| 618 |
-
- Se la classe è "Riutilizzo Funzionale", stop.
|
| 619 |
-
- Se "Upcycling Creativo", prosegui con generazione di soluzioni (VAE)...
|
| 620 |
|
| 621 |
-
|
| 622 |
-
-
|
| 623 |
-
- Finito l'allenamento, potrai generare configurazioni fittizie per l'upcycling (dim, spessore...).
|
| 624 |
|
| 625 |
-
|
| 626 |
-
-
|
| 627 |
|
| 628 |
-
|
| 629 |
-
|
| 630 |
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
############### RESET ############
|
| 635 |
def reset_app():
|
| 636 |
-
for k in ["data","models","
|
| 637 |
if k in st.session_state:
|
| 638 |
del st.session_state[k]
|
| 639 |
-
st.success("App
|
| 640 |
st.experimental_rerun()
|
| 641 |
|
| 642 |
-
|
|
|
|
|
|
|
| 643 |
def main():
|
| 644 |
-
st.sidebar.title("WEEKO
|
| 645 |
-
step= st.sidebar.radio("Fasi:",
|
| 646 |
-
"
|
| 647 |
-
"
|
| 648 |
-
"
|
| 649 |
-
"
|
| 650 |
-
"
|
| 651 |
-
"
|
| 652 |
])
|
| 653 |
-
st.sidebar.button("Reset App"
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
elif step=="
|
| 665 |
-
vae_training_phase()
|
| 666 |
-
elif step=="📊 Dashboard":
|
| 667 |
show_dashboard()
|
| 668 |
-
elif step=="
|
| 669 |
show_help()
|
| 670 |
|
| 671 |
if __name__=="__main__":
|
| 672 |
-
main()
|
| 673 |
-
|
|
|
|
| 5 |
import matplotlib.pyplot as plt
|
| 6 |
from statistics import mode, StatisticsError
|
| 7 |
|
| 8 |
+
# scikit-learn
|
| 9 |
+
from sklearn.model_selection import train_test_split
|
| 10 |
from sklearn.preprocessing import StandardScaler
|
| 11 |
from sklearn.pipeline import Pipeline
|
| 12 |
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score
|
| 13 |
+
from sklearn.ensemble import RandomForestClassifier
|
| 14 |
from sklearn.linear_model import LogisticRegression
|
| 15 |
from sklearn.svm import SVC
|
| 16 |
from sklearn.neural_network import MLPClassifier
|
| 17 |
|
| 18 |
+
# torch
|
| 19 |
import torch
|
| 20 |
import torch.nn as nn
|
| 21 |
import torch.nn.functional as F
|
| 22 |
|
| 23 |
+
# HF Transformers per generazione testo
|
| 24 |
from transformers import pipeline
|
| 25 |
|
| 26 |
+
#############################
|
| 27 |
+
# STREAMLIT config
|
| 28 |
+
#############################
|
| 29 |
+
st.set_page_config(page_title="WEEKO – 4 Step Flow", layout="wide")
|
| 30 |
|
| 31 |
+
#############################
|
| 32 |
+
# DIZIONARIO MODELLI ML
|
| 33 |
+
#############################
|
| 34 |
class DummyTabTransformerClassifier:
|
|
|
|
| 35 |
def __init__(self, input_dim=8):
|
| 36 |
+
self.clf = MLPClassifier(hidden_layer_sizes=(16,8), max_iter=100, random_state=42)
|
|
|
|
| 37 |
def fit(self, X, y):
|
| 38 |
+
self.clf.fit(X,y)
|
| 39 |
return self
|
| 40 |
def predict(self, X):
|
| 41 |
return self.clf.predict(X)
|
| 42 |
def predict_proba(self, X):
|
| 43 |
+
if hasattr(self.clf,"predict_proba"):
|
| 44 |
return self.clf.predict_proba(X)
|
| 45 |
else:
|
| 46 |
+
preds=self.clf.predict(X)
|
| 47 |
return np.array([[1.0,0.0] if p==0 else [0.0,1.0] for p in preds])
|
| 48 |
|
| 49 |
+
MODELS_ML = {
|
| 50 |
+
"RandomForest": RandomForestClassifier(random_state=42, n_estimators=100),
|
| 51 |
+
"LogisticRegression": LogisticRegression(random_state=42, max_iter=500),
|
| 52 |
+
"SVM": SVC(probability=True, random_state=42),
|
| 53 |
+
"TabTransformer(Dummy)": DummyTabTransformerClassifier()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
}
|
| 55 |
|
| 56 |
+
#############################
|
| 57 |
+
# VAE
|
| 58 |
+
#############################
|
| 59 |
class MiniVAE(nn.Module):
|
| 60 |
def __init__(self, input_dim=5, latent_dim=2):
|
| 61 |
super().__init__()
|
| 62 |
+
self.fc1 = nn.Linear(input_dim,32)
|
| 63 |
+
self.fc21= nn.Linear(32,latent_dim)
|
| 64 |
+
self.fc22= nn.Linear(32,latent_dim)
|
| 65 |
+
self.fc3 = nn.Linear(latent_dim,32)
|
| 66 |
+
self.fc4 = nn.Linear(32,input_dim)
|
| 67 |
+
def encode(self,x):
|
| 68 |
+
h=F.relu(self.fc1(x))
|
|
|
|
| 69 |
return self.fc21(h), self.fc22(h)
|
|
|
|
| 70 |
def reparameterize(self, mu, logvar):
|
| 71 |
+
std = torch.exp(0.5*logvar)
|
| 72 |
eps = torch.randn_like(std)
|
| 73 |
return mu + eps*std
|
| 74 |
+
def decode(self,z):
|
| 75 |
+
h=F.relu(self.fc3(z))
|
|
|
|
| 76 |
return self.fc4(h)
|
| 77 |
+
def forward(self,x):
|
| 78 |
+
mu,logvar=self.encode(x)
|
| 79 |
+
z=self.reparameterize(mu,logvar)
|
| 80 |
+
recon=self.decode(z)
|
|
|
|
| 81 |
return recon, mu, logvar
|
| 82 |
|
| 83 |
+
def vae_loss(recon_x,x,mu,logvar):
|
| 84 |
+
mse = F.mse_loss(recon_x,x,reduction='sum')
|
| 85 |
+
kld = -0.5*torch.sum(1 + logvar - mu.pow(2)-logvar.exp())
|
| 86 |
+
return mse+kld
|
| 87 |
+
|
| 88 |
+
#############################
|
| 89 |
+
# UTILITY
|
| 90 |
+
#############################
|
| 91 |
+
SHAPE_MAPPING = {"axisymmetric":0,"sheet_metal":1,"alloy_plate":2,"complex_plastic":3}
|
| 92 |
+
ML_FEATURES = ["length","width","shape_code","weight","thickness","RUL","margin","compat_dim"]
|
| 93 |
+
VAE_FEATURES= ["length","width","weight","thickness","shape_code"]
|
| 94 |
+
|
| 95 |
+
def dimension_match(r, target_len, target_wid, t_shape, t_w, t_th, tol_len, tol_wid, tol_we, tol_th):
|
| 96 |
+
c_len= abs(r["length"]-target_len)<=tol_len
|
| 97 |
+
c_wid= abs(r["width"]-target_wid)<=tol_wid
|
| 98 |
+
c_shp= (r["shape"]==t_shape)
|
| 99 |
+
c_wei= abs(r["weight"]-t_w)<= tol_we
|
| 100 |
+
c_thi= abs(r["thickness"]-t_th)<= tol_th
|
| 101 |
+
return 1 if (c_len and c_wid and c_shp and c_wei and c_thi) else 0
|
| 102 |
+
|
| 103 |
+
def assign_class(r, thr_score=0.5, alpha=0.5, beta=0.5):
|
| 104 |
+
rul_norm = r["RUL"]/1000.0
|
| 105 |
+
margin_norm=(r["margin"]+200)/800.0
|
| 106 |
+
score= alpha*rul_norm + beta*margin_norm
|
| 107 |
+
if r["compat_dim"]==1 and score>=thr_score:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
return "Riutilizzo Funzionale"
|
| 109 |
else:
|
| 110 |
return "Upcycling Creativo"
|
| 111 |
|
| 112 |
+
#############################
|
| 113 |
+
# Step 1: Dataset
|
| 114 |
+
#############################
|
| 115 |
+
def step1_dataset():
|
| 116 |
+
st.header("Step 1: Dataset")
|
| 117 |
+
# Genera / Carica
|
| 118 |
+
col1,col2 = st.columns(2)
|
| 119 |
+
with col1:
|
| 120 |
+
data_choice= st.radio("Fonte Dati",["Genera","Carica CSV"],horizontal=True)
|
| 121 |
+
data=None
|
| 122 |
+
if data_choice=="Genera":
|
| 123 |
+
n= st.slider("Campioni sintetici",100,2000,300,step=50)
|
| 124 |
+
if st.button("Genera"):
|
| 125 |
+
data= generate_synthetic_data(n_samples=n)
|
| 126 |
+
st.session_state["data_source"]="generated"
|
| 127 |
else:
|
| 128 |
+
upl=st.file_uploader("Carica CSV con col: length,width,RUL,margin,shape,weight,thickness",type=["csv"])
|
| 129 |
+
if upl:
|
| 130 |
+
df= pd.read_csv(upl)
|
| 131 |
+
needed_cols=["length","width","RUL","margin","shape","weight","thickness"]
|
| 132 |
+
if not all(c in df.columns for c in needed_cols):
|
| 133 |
+
st.error("CSV non valido. Manca qualche colonna.")
|
| 134 |
+
else:
|
| 135 |
+
data=df
|
| 136 |
+
st.session_state["data_source"]="uploaded"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
if data is None:
|
| 138 |
+
st.info("Genera o carica un dataset per proseguire.")
|
|
|
|
|
|
|
|
|
|
| 139 |
return
|
| 140 |
|
| 141 |
+
# Param compat
|
| 142 |
+
with col2:
|
| 143 |
+
st.subheader("Param Compatibilità")
|
| 144 |
+
t_len= st.number_input("Lunghezza target (mm)",50.0,300.0,100.0)
|
| 145 |
+
t_wid= st.number_input("Larghezza target (mm)",20.0,200.0,50.0)
|
| 146 |
+
t_shp= st.selectbox("Forma target", list(SHAPE_MAPPING.keys()))
|
| 147 |
+
t_we = st.number_input("Peso target (kg)",5.0,300.0,80.0)
|
| 148 |
+
t_th = st.number_input("Spessore target (mm)",0.5,50.0,8.0)
|
| 149 |
+
tol_len = st.slider("Tol len ±",0.0,30.0,5.0)
|
| 150 |
+
tol_wid = st.slider("Tol wid ±",0.0,20.0,3.0)
|
| 151 |
+
tol_wei = st.slider("Tol weight ±",0.0,50.0,10.0)
|
| 152 |
+
tol_thi = st.slider("Tol thick ±",0.0,5.0,1.0)
|
| 153 |
+
st.markdown("**Score RUL & margin**")
|
| 154 |
+
thr= st.slider("Soglia score",0.0,1.0,0.5)
|
| 155 |
+
alpha= st.slider("Peso RUL(α)",0.0,1.0,0.5)
|
| 156 |
+
beta= st.slider("Peso Margin(β)",0.0,1.0,0.5)
|
| 157 |
+
|
| 158 |
+
# Prepara dataset
|
| 159 |
+
data['shape_code']= data['shape'].map(SHAPE_MAPPING).fillna(-1).astype(int)
|
| 160 |
+
data['compat_dim']= data.apply(lambda r: dimension_match(r,t_len,t_wid,t_shp,t_we,t_th,tol_len,tol_wid,tol_wei,tol_thi), axis=1)
|
| 161 |
+
data['Target'] = data.apply(lambda r: assign_class(r,thr_score=thr,alpha=alpha,beta=beta), axis=1)
|
| 162 |
+
|
| 163 |
+
st.dataframe(data.head(10))
|
| 164 |
+
st.write("Distribuzione classi:", data["Target"].value_counts())
|
| 165 |
+
|
| 166 |
+
st.session_state["data"] = data
|
| 167 |
+
st.session_state["params_dim"]={
|
| 168 |
+
"target_len": t_len, "target_wid": t_wid,
|
| 169 |
+
"target_shp": t_shp, "target_we": t_we, "target_th": t_th,
|
| 170 |
+
"tol_len": tol_len,"tol_wid": tol_wid,"tol_wei":tol_wei,"tol_thi":tol_thi,
|
| 171 |
+
"thr_score":thr,"alpha":alpha,"beta":beta
|
| 172 |
+
}
|
| 173 |
+
csv_data= data.to_csv(index=False).encode('utf-8')
|
| 174 |
+
st.download_button("Scarica dataset elaborato",csv_data,"dataset_processed.csv","text/csv")
|
| 175 |
+
|
| 176 |
+
#############################
|
| 177 |
+
# Step 2: Addestramento ML
|
| 178 |
+
#############################
|
| 179 |
+
def step2_trainML():
|
| 180 |
+
st.header("Step 2: Addestramento ML (Riutilizzo vs Upcycling)")
|
| 181 |
+
data= st.session_state.get("data",None)
|
| 182 |
+
if data is None:
|
| 183 |
+
st.error("Prima prepara dataset in Step 1.")
|
| 184 |
+
return
|
| 185 |
+
if "Target" not in data.columns:
|
| 186 |
+
st.error("Colonna 'Target' mancante. Rivedi step 1.")
|
| 187 |
return
|
| 188 |
|
| 189 |
+
features_ml=[f for f in ML_FEATURES if f in data.columns]
|
| 190 |
+
if not features_ml:
|
| 191 |
+
st.error(f"Mancano feature minime per ML: {ML_FEATURES}")
|
| 192 |
+
return
|
| 193 |
+
X= data[features_ml].copy()
|
| 194 |
+
y= data["Target"].map({"Riutilizzo Funzionale":0,"Upcycling Creativo":1})
|
| 195 |
if len(y.unique())<2:
|
| 196 |
st.error("Il dataset ha una sola classe. Non si può addestrare.")
|
| 197 |
return
|
| 198 |
+
from sklearn.model_selection import train_test_split
|
| 199 |
+
X_train,X_test,y_train,y_test= train_test_split(X,y,test_size=0.25,random_state=42,stratify=y)
|
| 200 |
st.write(f"Train={len(X_train)}, Test={len(X_test)}")
|
| 201 |
|
| 202 |
+
# Addestra
|
| 203 |
+
trained_pipes={}
|
| 204 |
results=[]
|
| 205 |
+
for nome, model in MODELS_ML.items():
|
| 206 |
+
st.subheader(f"Modello: {nome}")
|
|
|
|
| 207 |
from sklearn.pipeline import Pipeline
|
| 208 |
+
pipe= Pipeline([
|
| 209 |
+
("scaler", StandardScaler()),
|
| 210 |
("clf",model)
|
| 211 |
])
|
| 212 |
try:
|
| 213 |
+
pipe.fit(X_train,y_train)
|
| 214 |
+
y_pred= pipe.predict(X_test)
|
| 215 |
+
acc= accuracy_score(y_test,y_pred)
|
| 216 |
+
f1= f1_score(y_test,y_pred,average='weighted')
|
| 217 |
+
results.append({"Modello":nome,"Accuracy":acc,"F1 Score":f1})
|
| 218 |
+
trained_pipes[nome]= pipe
|
| 219 |
+
|
| 220 |
+
cm= confusion_matrix(y_test,y_pred)
|
| 221 |
+
fig, ax= plt.subplots()
|
| 222 |
+
sns.heatmap(cm, annot=True, fmt='d', cmap="Greens", ax=ax)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
plt.xlabel("Pred")
|
| 224 |
plt.ylabel("True")
|
| 225 |
st.pyplot(fig)
|
| 226 |
+
|
| 227 |
st.metric("Accuracy",f"{acc:.3f}")
|
| 228 |
st.metric("F1 Score",f"{f1:.3f}")
|
|
|
|
| 229 |
except Exception as e:
|
| 230 |
+
st.error(f"Errore addestramento {nome}: {e}")
|
| 231 |
|
| 232 |
if results:
|
| 233 |
+
df_r= pd.DataFrame(results).sort_values(by="Accuracy",ascending=False)
|
| 234 |
+
st.dataframe(df_r)
|
| 235 |
+
st.session_state["models"]= trained_pipes
|
| 236 |
+
st.session_state["ml_results"]= df_r
|
| 237 |
else:
|
| 238 |
st.error("Nessun modello addestrato con successo.")
|
| 239 |
+
st.session_state["models"]=None
|
| 240 |
|
| 241 |
+
#############################
|
| 242 |
+
# Step 2B: Training VAE
|
| 243 |
+
#############################
|
| 244 |
+
def step2b_trainVAE():
|
| 245 |
+
st.header("Step 2B: Training VAE (per Upcycling)")
|
| 246 |
|
| 247 |
+
data=st.session_state.get("data",None)
|
|
|
|
|
|
|
| 248 |
if data is None:
|
| 249 |
+
st.error("Prima prepara dataset in Step 1.")
|
| 250 |
return
|
| 251 |
+
feats= [f for f in VAE_FEATURES if f in data.columns]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
if not feats:
|
| 253 |
+
st.error(f"Mancano feature per VAE: {VAE_FEATURES}")
|
| 254 |
return
|
| 255 |
+
st.write("Useremo le feature:", feats)
|
| 256 |
|
| 257 |
+
lat_dim= st.slider("Dim latente",2,10,2)
|
| 258 |
+
ep= st.number_input("Epoch",10,300,50)
|
| 259 |
+
lr= st.number_input("Learning Rate",1e-5,1e-2,1e-3,format="%e")
|
| 260 |
+
bs= st.selectbox("Batch size",[16,32,64], index=1)
|
| 261 |
|
| 262 |
+
if not st.session_state.get("vae_trained",False):
|
| 263 |
+
st.warning("VAE non ancora addestrato.")
|
| 264 |
+
if st.button("Allena VAE"):
|
| 265 |
+
# Inizializzo
|
| 266 |
st.session_state["vae"]= MiniVAE(input_dim=len(feats), latent_dim=lat_dim)
|
|
|
|
|
|
|
| 267 |
from sklearn.preprocessing import StandardScaler
|
| 268 |
X_vae= data[feats].copy()
|
|
|
|
| 269 |
for c in X_vae.columns:
|
| 270 |
if X_vae[c].isnull().any():
|
| 271 |
+
X_vae[c].fillna(X_vae[c].median(),inplace=True)
|
|
|
|
|
|
|
| 272 |
scaler= StandardScaler()
|
| 273 |
+
X_s= scaler.fit_transform(X_vae)
|
| 274 |
st.session_state["vae_scaler"]= scaler
|
| 275 |
|
| 276 |
+
dataset= torch.utils.data.TensorDataset(torch.tensor(X_s,dtype=torch.float32))
|
| 277 |
loader= torch.utils.data.DataLoader(dataset,batch_size=bs,shuffle=True)
|
| 278 |
+
vae= st.session_state["vae"]
|
| 279 |
+
opt= torch.optim.Adam(vae.parameters(),lr=lr)
|
|
|
|
| 280 |
losses=[]
|
| 281 |
vae.train()
|
| 282 |
for epoch in range(int(ep)):
|
| 283 |
+
ep_loss=0
|
| 284 |
for (batch,) in loader:
|
| 285 |
+
opt.zero_grad()
|
| 286 |
recon, mu, logvar= vae(batch)
|
| 287 |
+
loss= vae_loss(recon,batch,mu,logvar)
|
| 288 |
loss.backward()
|
| 289 |
+
opt.step()
|
| 290 |
+
ep_loss+=loss.item()
|
| 291 |
+
avgL= ep_loss/len(dataset)
|
| 292 |
+
losses.append(avgL)
|
| 293 |
st.progress((epoch+1)/ep)
|
| 294 |
+
st.success(f"Training VAE completato, Loss ~ {avgL:.2f}")
|
|
|
|
| 295 |
st.line_chart(losses)
|
| 296 |
+
st.session_state["vae_trained"]= True
|
| 297 |
else:
|
| 298 |
+
st.success("VAE già addestrato. Se vuoi rifarlo, resetta e ricarica.")
|
| 299 |
+
|
| 300 |
+
#############################
|
| 301 |
+
# STEP 3: Upcycling Generative
|
| 302 |
+
#############################
|
| 303 |
+
def step3_upcycling_generative():
|
| 304 |
+
st.header("Step 3: Upcycling Generative – Genera idee con VAE + GenAI")
|
| 305 |
+
|
| 306 |
+
# Verifica se VAE c'è
|
| 307 |
+
if not st.session_state.get("vae_trained",False):
|
| 308 |
+
st.error("Devi addestrare il VAE (Step 2B) prima.")
|
| 309 |
+
return
|
| 310 |
+
vae= st.session_state.get("vae",None)
|
| 311 |
+
vae_scaler= st.session_state.get("vae_scaler",None)
|
| 312 |
+
if vae is None or vae_scaler is None:
|
| 313 |
+
st.error("VAE o scaler mancanti.")
|
| 314 |
+
return
|
| 315 |
+
|
| 316 |
+
st.write("Inserisci manualmente i param EoL (opzionale) oppure generiamo n idee random dal VAE?")
|
| 317 |
|
| 318 |
+
if st.checkbox("Usa param EoL come base? (NON implementato)"):
|
| 319 |
+
st.info("Potresti implementare un partial-conditional VAE. Attualmente generiamo in modo random lat space.")
|
| 320 |
+
# In un VAE classico non-conditional, non possiamo forzare i param a una base specifica, a meno di trick
|
| 321 |
+
|
| 322 |
+
n_ideas= st.number_input("N. idee da generare",1,10,3)
|
| 323 |
+
if st.button("Genera Upcycling"):
|
| 324 |
+
vae.eval()
|
| 325 |
+
with torch.no_grad():
|
| 326 |
+
lat_dim= vae.fc21.out_features
|
| 327 |
+
z= torch.randn(n_ideas, lat_dim)
|
| 328 |
+
recon= vae.decode(z)
|
| 329 |
+
arr= recon.numpy()
|
| 330 |
+
try:
|
| 331 |
+
arr_inv= vae_scaler.inverse_transform(arr)
|
| 332 |
+
df_gen= pd.DataFrame(arr_inv, columns=vae_scaler.feature_names_in_)
|
| 333 |
+
|
| 334 |
+
# shape_code -> shape
|
| 335 |
+
if 'shape_code' in df_gen.columns:
|
| 336 |
+
df_gen['shape_code']= df_gen['shape_code'].round().astype(int)
|
| 337 |
+
rev_map= {v:k for k,v in SHAPE_MAPPING.items()}
|
| 338 |
+
df_gen['shape']= df_gen['shape_code'].map(rev_map).fillna('unknown')
|
| 339 |
+
|
| 340 |
+
st.subheader("Configurazioni Generate (VAE)")
|
| 341 |
+
st.dataframe(df_gen.round(2))
|
| 342 |
+
|
| 343 |
+
# Gen testo con pipeline HF
|
| 344 |
+
st.markdown("### Suggerimenti Testuali di Upcycling")
|
| 345 |
+
text_generator = pipeline("text-generation", model="distilgpt2",
|
| 346 |
+
device=0 if torch.cuda.is_available() else -1)
|
| 347 |
+
|
| 348 |
+
def gen_upcycle_text(shape, thick, wei):
|
| 349 |
+
prompt= (
|
| 350 |
+
f"Ho un componente EoL di forma {shape}, spessore {thick:.1f} mm, peso {wei:.1f} kg.\n"
|
| 351 |
+
"Dammi un'idea creativa di upcycling in italiano, con passaggi principali:"
|
| 352 |
+
)
|
| 353 |
+
out= text_generator(prompt, max_new_tokens=50, do_sample=True, top_k=50)
|
| 354 |
+
return out[0]["generated_text"]
|
| 355 |
+
|
| 356 |
+
for i, row in df_gen.iterrows():
|
| 357 |
+
s= row.get("shape","unknown")
|
| 358 |
+
t= row.get("thickness",1.0)
|
| 359 |
+
w= row.get("weight",10.0)
|
| 360 |
+
text_sug= gen_upcycle_text(s,t,w)
|
| 361 |
+
st.write(f"**Idea {i+1}**: shape={s}, thickness={t:.1f}, weight={w:.1f}")
|
| 362 |
+
st.info(text_sug)
|
| 363 |
+
st.markdown("---")
|
| 364 |
+
|
| 365 |
+
except Exception as e:
|
| 366 |
+
st.error(f"Errore decodifica VAE: {e}")
|
| 367 |
+
|
| 368 |
+
#############################
|
| 369 |
+
# DASHBOARD
|
| 370 |
+
#############################
|
| 371 |
def show_dashboard():
|
| 372 |
+
st.header("Dashboard")
|
| 373 |
+
data= st.session_state.get("data",None)
|
| 374 |
if data is None:
|
| 375 |
st.error("Nessun dataset.")
|
| 376 |
return
|
| 377 |
+
st.write("Distribuzione classi:\n", data["Target"].value_counts())
|
| 378 |
|
| 379 |
+
if 'ml_results' in st.session_state and st.session_state["ml_results"] is not None:
|
| 380 |
+
st.subheader("Risultati ML")
|
| 381 |
+
st.dataframe(st.session_state["ml_results"])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
else:
|
| 383 |
+
st.info("Nessun risultato ML salvato.")
|
| 384 |
|
| 385 |
+
if st.session_state.get("vae_trained",False):
|
| 386 |
+
st.success("VAE addestrato.")
|
|
|
|
| 387 |
else:
|
| 388 |
+
st.warning("VAE non addestrato.")
|
| 389 |
|
| 390 |
+
#############################
|
| 391 |
+
# HELP
|
| 392 |
+
#############################
|
| 393 |
def show_help():
|
| 394 |
+
st.header("ℹ️ Guida all'Uso - Quattro Step")
|
| 395 |
st.markdown("""
|
| 396 |
+
**Flusso**:
|
| 397 |
+
1. **Step 1: Dataset**
|
| 398 |
+
- Generi o carichi CSV, definisci param. compatibilità, ottieni classi 'Riutilizzo' vs 'Upcycling'.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
|
| 400 |
+
2. **Step 2: Addestramento ML**
|
| 401 |
+
- Alleni modelli per predire su nuovi EoL.
|
|
|
|
|
|
|
| 402 |
|
| 403 |
+
3. **Step 2B: Training VAE**
|
| 404 |
+
- Alleni VAE su feature geometriche, per generare configurazioni creative.
|
|
|
|
| 405 |
|
| 406 |
+
4. **Step 3: Upcycling Generative**
|
| 407 |
+
- Generi N idee dal VAE + un modello di text-generation (distilgpt2) fornisce suggerimenti creativi in italiano.
|
| 408 |
|
| 409 |
+
**Dashboard**: mostra le metriche finali.
|
| 410 |
+
""")
|
| 411 |
|
| 412 |
+
#############################
|
| 413 |
+
# RESET
|
| 414 |
+
#############################
|
|
|
|
| 415 |
def reset_app():
|
| 416 |
+
for k in ["data","models","ml_results","vae","vae_trained","vae_scaler","data_source","params_dim"]:
|
| 417 |
if k in st.session_state:
|
| 418 |
del st.session_state[k]
|
| 419 |
+
st.success("App resettata.")
|
| 420 |
st.experimental_rerun()
|
| 421 |
|
| 422 |
+
#############################
|
| 423 |
+
# MAIN
|
| 424 |
+
#############################
|
| 425 |
def main():
|
| 426 |
+
st.sidebar.title("WEEKO 4 Step Flow")
|
| 427 |
+
step = st.sidebar.radio("Fasi:",[
|
| 428 |
+
"Step 1: Dataset",
|
| 429 |
+
"Step 2: Addestramento ML",
|
| 430 |
+
"Step 2B: Training VAE",
|
| 431 |
+
"Step 3: Upcycling Generative",
|
| 432 |
+
"Dashboard",
|
| 433 |
+
"Help"
|
| 434 |
])
|
| 435 |
+
if st.sidebar.button("Reset App"):
|
| 436 |
+
reset_app()
|
| 437 |
+
|
| 438 |
+
if step=="Step 1: Dataset":
|
| 439 |
+
step1_dataset()
|
| 440 |
+
elif step=="Step 2: Addestramento ML":
|
| 441 |
+
step2_trainML()
|
| 442 |
+
elif step=="Step 2B: Training VAE":
|
| 443 |
+
step2b_trainVAE()
|
| 444 |
+
elif step=="Step 3: Upcycling Generative":
|
| 445 |
+
step3_upcycling_generative()
|
| 446 |
+
elif step=="Dashboard":
|
|
|
|
|
|
|
| 447 |
show_dashboard()
|
| 448 |
+
elif step=="Help":
|
| 449 |
show_help()
|
| 450 |
|
| 451 |
if __name__=="__main__":
|
| 452 |
+
main()
|
|
|