valegro commited on
Commit
5037b39
·
verified ·
1 Parent(s): 520455f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +174 -156
app.py CHANGED
@@ -20,17 +20,42 @@ 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)
@@ -53,9 +78,9 @@ MODELS_ML = {
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__()
@@ -65,7 +90,7 @@ class MiniVAE(nn.Module):
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)
@@ -82,131 +107,127 @@ class MiniVAE(nn.Module):
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:
@@ -214,8 +235,8 @@ def step2_trainML():
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()
@@ -227,26 +248,26 @@ def step2_trainML():
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:
@@ -254,21 +275,20 @@ def step2b_trainVAE():
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
@@ -277,6 +297,7 @@ def step2b_trainVAE():
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)):
@@ -291,127 +312,124 @@ def step2b_trainVAE():
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:
@@ -419,12 +437,12 @@ def reset_app():
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",
 
20
  import torch.nn as nn
21
  import torch.nn.functional as F
22
 
23
+ # Transformers per la GenAI testuale
24
  from transformers import pipeline
25
 
26
+ ##################################################
27
+ # 1) FUNZIONE generate_synthetic_data (PRIMA DELL'USO)
28
+ ##################################################
29
+ def generate_synthetic_data(n_samples=300, seed=42):
30
+ """
31
+ Genera un dataset sintetico con:
32
+ - length, width, RUL, margin, shape, weight, thickness
33
+ - shape pescata da ["axisymmetric","sheet_metal","alloy_plate","complex_plastic"]
34
+ - RUL e margin con maggiore varianza
35
+ """
36
+ np.random.seed(seed)
37
+ length = np.clip(np.random.normal(100,20,n_samples), 50, 250)
38
+ width = np.clip(np.random.normal(50,15,n_samples), 20, 150)
39
+ RUL = np.clip(np.random.normal(500,250,n_samples), 0, 1000).astype(int)
40
+ margin = np.clip(np.random.normal(150,150,n_samples), -200,600).astype(int)
41
+ shapes = np.random.choice(["axisymmetric","sheet_metal","alloy_plate","complex_plastic"],
42
+ size=n_samples, p=[0.4,0.3,0.2,0.1])
43
+ weight = np.clip(np.random.normal(80,30,n_samples), 10, 250)
44
+ thickness = np.clip(np.random.normal(8,4,n_samples), 0.5, 30)
45
+
46
+ return pd.DataFrame({
47
+ 'length': length,
48
+ 'width': width,
49
+ 'RUL': RUL,
50
+ 'margin': margin,
51
+ 'shape': shapes,
52
+ 'weight': weight,
53
+ 'thickness': thickness
54
+ })
55
+
56
+ ##################################################
57
+ # 2) MODELLI ML PLACEHOLDER
58
+ ##################################################
59
  class DummyTabTransformerClassifier:
60
  def __init__(self, input_dim=8):
61
  self.clf = MLPClassifier(hidden_layer_sizes=(16,8), max_iter=100, random_state=42)
 
78
  "TabTransformer(Dummy)": DummyTabTransformerClassifier()
79
  }
80
 
81
+ ##################################################
82
+ # 3) VAE PER LA PARTE GENERATIVA (UPCYCLING)
83
+ ##################################################
84
  class MiniVAE(nn.Module):
85
  def __init__(self, input_dim=5, latent_dim=2):
86
  super().__init__()
 
90
  self.fc3 = nn.Linear(latent_dim,32)
91
  self.fc4 = nn.Linear(32,input_dim)
92
  def encode(self,x):
93
+ h = F.relu(self.fc1(x))
94
  return self.fc21(h), self.fc22(h)
95
  def reparameterize(self, mu, logvar):
96
  std = torch.exp(0.5*logvar)
 
107
 
108
  def vae_loss(recon_x,x,mu,logvar):
109
  mse = F.mse_loss(recon_x,x,reduction='sum')
110
+ kld = -0.5*torch.sum(1+logvar - mu.pow(2)-logvar.exp())
111
  return mse+kld
112
 
113
+ ##################################################
114
+ # 4) COSTANTI E MAPPING FEATURE
115
+ ##################################################
116
  SHAPE_MAPPING = {"axisymmetric":0,"sheet_metal":1,"alloy_plate":2,"complex_plastic":3}
117
+ ML_FEATURES = ["length","width","shape_code","weight","thickness","RUL","margin","compat_dim"]
118
+ VAE_FEATURES = ["length","width","weight","thickness","shape_code"]
119
+
120
+ ##################################################
121
+ # 5) UTILITY: dimension_match e assign_class
122
+ ##################################################
123
+ def dimension_match(r, target_len, target_wid, t_shape, t_w, t_th,
124
+ tol_len, tol_wid, tol_we, tol_th):
125
  c_len= abs(r["length"]-target_len)<=tol_len
126
+ c_wid= abs(r["width"]-target_wid)<= tol_wid
127
  c_shp= (r["shape"]==t_shape)
128
+ c_wei= abs(r["weight"]-t_w)<=tol_we
129
+ c_thi= abs(r["thickness"]-t_th)<=tol_th
130
  return 1 if (c_len and c_wid and c_shp and c_wei and c_thi) else 0
131
 
132
  def assign_class(r, thr_score=0.5, alpha=0.5, beta=0.5):
133
  rul_norm = r["RUL"]/1000.0
134
+ margin_norm= (r["margin"]+200)/800.0
135
  score= alpha*rul_norm + beta*margin_norm
136
  if r["compat_dim"]==1 and score>=thr_score:
137
  return "Riutilizzo Funzionale"
138
  else:
139
  return "Upcycling Creativo"
140
 
141
+ ##################################################
142
+ # STEP 1: DATASET
143
+ ##################################################
144
  def step1_dataset():
145
  st.header("Step 1: Dataset")
146
+
147
+ colA, colB = st.columns(2)
148
+ with colA:
149
+ data_opt= st.radio("Fonte Dati", ["Genera","Carica CSV"], horizontal=True)
150
  data=None
151
+ if data_opt=="Genera":
152
+ n= st.slider("Campioni sintetici",100,2000,300,step=100)
153
  if st.button("Genera"):
154
  data= generate_synthetic_data(n_samples=n)
155
  st.session_state["data_source"]="generated"
156
  else:
157
+ upl= st.file_uploader("Carica CSV con col. minime [length,width,RUL,margin,shape,weight,thickness]", type=["csv"])
158
  if upl:
159
  df= pd.read_csv(upl)
160
+ needed=["length","width","RUL","margin","shape","weight","thickness"]
161
+ if not all(c in df.columns for c in needed):
162
  st.error("CSV non valido. Manca qualche colonna.")
163
  else:
164
  data=df
165
  st.session_state["data_source"]="uploaded"
 
 
 
166
 
167
+ with colB:
168
+ st.markdown("**Parametri Compatibilità**")
169
+ t_len= st.number_input("Lunghezza target",50.0,300.0,100.0)
170
+ t_wid= st.number_input("Larghezza target",20.0,200.0,50.0)
 
171
  t_shp= st.selectbox("Forma target", list(SHAPE_MAPPING.keys()))
172
+ t_wei= st.number_input("Peso target (kg)",5.0,300.0,80.0)
173
+ t_thi= st.number_input("Spessore target (mm)",0.5,50.0,8.0)
174
+
175
+ st.markdown("**Tolleranze**")
176
+ tol_len= st.slider("Tol len ±",0.0,30.0,5.0)
177
+ tol_wid= st.slider("Tol wid ±",0.0,20.0,3.0)
178
+ tol_wei= st.slider("Tol weight ±",0.0,50.0,10.0)
179
+ tol_thi= st.slider("Tol thick ±",0.0,5.0,1.0)
180
+
181
+ st.markdown("**Score RUL & Margin**")
182
+ thr= st.slider("Soglia Score",0.0,1.0,0.5)
183
  alpha= st.slider("Peso RUL(α)",0.0,1.0,0.5)
184
  beta= st.slider("Peso Margin(β)",0.0,1.0,0.5)
185
 
186
+ if data is not None:
187
+ data['shape_code']= data['shape'].map(SHAPE_MAPPING).fillna(-1).astype(int)
188
+ data['compat_dim']= data.apply(lambda r: dimension_match(r, t_len, t_wid, t_shp, t_wei, t_thi,
189
+ tol_len, tol_wid, tol_wei, tol_thi),
190
+ axis=1)
191
+ data['Target'] = data.apply(lambda r: assign_class(r, thr_score=thr, alpha=alpha, beta=beta), axis=1)
192
+
193
+ st.dataframe(data.head(10))
194
+ st.write("Distrib. Target:", data["Target"].value_counts())
195
+ st.session_state["data"]= data
196
+ csv=data.to_csv(index=False).encode('utf-8')
197
+ st.download_button("Scarica dataset elaborato", csv, "dataset_processed.csv")
198
+
199
+ ##################################################
200
+ # STEP 2: ADD ML
201
+ ##################################################
 
 
 
 
 
202
  def step2_trainML():
203
+ st.header("Step 2: Addestramento ML")
204
  data= st.session_state.get("data",None)
205
  if data is None:
206
+ st.error("Devi completare Step 1.")
207
  return
208
  if "Target" not in data.columns:
209
+ st.error("Manca colonna 'Target'. Rivedi Step 1.")
210
  return
211
 
212
  features_ml=[f for f in ML_FEATURES if f in data.columns]
213
  if not features_ml:
214
+ st.error("Mancano feature minime ML.")
215
  return
216
+ X= data[features_ml]
217
  y= data["Target"].map({"Riutilizzo Funzionale":0,"Upcycling Creativo":1})
218
  if len(y.unique())<2:
219
+ st.error("Dataset ha una sola classe. Impossibile train.")
220
  return
 
221
  X_train,X_test,y_train,y_test= train_test_split(X,y,test_size=0.25,random_state=42,stratify=y)
222
  st.write(f"Train={len(X_train)}, Test={len(X_test)}")
223
 
224
+ trained={}
 
225
  results=[]
226
+ for nome,model in MODELS_ML.items():
227
  st.subheader(f"Modello: {nome}")
228
  from sklearn.pipeline import Pipeline
229
  pipe= Pipeline([
230
+ ("scaler",StandardScaler()),
231
  ("clf",model)
232
  ])
233
  try:
 
235
  y_pred= pipe.predict(X_test)
236
  acc= accuracy_score(y_test,y_pred)
237
  f1= f1_score(y_test,y_pred,average='weighted')
238
+ results.append({"Modello":nome,"Accuracy":acc,"F1":f1})
239
+ trained[nome]= pipe
240
 
241
  cm= confusion_matrix(y_test,y_pred)
242
  fig, ax= plt.subplots()
 
248
  st.metric("Accuracy",f"{acc:.3f}")
249
  st.metric("F1 Score",f"{f1:.3f}")
250
  except Exception as e:
251
+ st.error(f"Errore training {nome}: {e}")
252
 
253
  if results:
254
+ df_r= pd.DataFrame(results).sort_values(by="Accuracy", ascending=False)
255
  st.dataframe(df_r)
256
+ st.session_state["models"]= trained
257
  st.session_state["ml_results"]= df_r
258
  else:
259
+ st.error("Nessun modello addestrato.")
260
  st.session_state["models"]=None
261
 
262
+ ##################################################
263
+ # STEP 2B: TRAIN VAE
264
+ ##################################################
265
  def step2b_trainVAE():
266
+ st.header("Step 2B: Training VAE per Upcycling")
267
 
268
+ data= st.session_state.get("data",None)
269
  if data is None:
270
+ st.error("Completa Step 1.")
271
  return
272
  feats= [f for f in VAE_FEATURES if f in data.columns]
273
  if not feats:
 
275
  return
276
  st.write("Useremo le feature:", feats)
277
 
278
+ lat_dim= st.slider("Dim latente VAE",2,10,2)
279
+ ep= st.number_input("Epochs",10,300,50)
280
+ lr= st.number_input("Learning Rate",1e-5,1e-2,1e-3, format="%e")
281
+ bs= st.selectbox("Batch size",[16,32,64],index=1)
282
 
283
  if not st.session_state.get("vae_trained",False):
284
+ st.warning("VAE non addestrato")
285
  if st.button("Allena VAE"):
 
286
  st.session_state["vae"]= MiniVAE(input_dim=len(feats), latent_dim=lat_dim)
287
  from sklearn.preprocessing import StandardScaler
288
  X_vae= data[feats].copy()
289
  for c in X_vae.columns:
290
  if X_vae[c].isnull().any():
291
+ X_vae[c].fillna(X_vae[c].median(), inplace=True)
292
  scaler= StandardScaler()
293
  X_s= scaler.fit_transform(X_vae)
294
  st.session_state["vae_scaler"]= scaler
 
297
  loader= torch.utils.data.DataLoader(dataset,batch_size=bs,shuffle=True)
298
  vae= st.session_state["vae"]
299
  opt= torch.optim.Adam(vae.parameters(),lr=lr)
300
+
301
  losses=[]
302
  vae.train()
303
  for epoch in range(int(ep)):
 
312
  avgL= ep_loss/len(dataset)
313
  losses.append(avgL)
314
  st.progress((epoch+1)/ep)
315
+ st.success(f"VAE addestrato (Loss ~ {avgL:.2f})")
316
  st.line_chart(losses)
317
  st.session_state["vae_trained"]= True
318
  else:
319
+ st.success("VAE già addestrato.")
320
+ if st.button("Riallena"):
321
+ st.session_state["vae_trained"]=False
322
+ st.experimental_rerun()
323
 
324
+ ##################################################
325
  # STEP 3: Upcycling Generative
326
+ ##################################################
327
  def step3_upcycling_generative():
328
+ st.header("Step 3: Upcycling Generative - VAE + GenAI")
329
 
 
330
  if not st.session_state.get("vae_trained",False):
331
+ st.error("Devi addestrare il VAE in Step 2B prima.")
332
  return
333
  vae= st.session_state.get("vae",None)
334
  vae_scaler= st.session_state.get("vae_scaler",None)
335
  if vae is None or vae_scaler is None:
336
+ st.error("Mancano vae o scaler.")
337
  return
338
 
339
+ lat_dim= vae.fc21.out_features
340
+ st.write(f"VAE con lat_dim={lat_dim}. Generiamo idee upcycling.")
341
 
342
+ n_ideas= st.number_input("Quante idee generare",1,10,3)
 
 
 
 
343
  if st.button("Genera Upcycling"):
344
  vae.eval()
345
  with torch.no_grad():
346
+ z=torch.randn(n_ideas,lat_dim)
 
347
  recon= vae.decode(z)
348
  arr= recon.numpy()
349
  try:
350
+ df_gen= pd.DataFrame(vae_scaler.inverse_transform(arr), columns=vae_scaler.feature_names_in_)
 
 
351
  # shape_code -> shape
352
  if 'shape_code' in df_gen.columns:
353
  df_gen['shape_code']= df_gen['shape_code'].round().astype(int)
354
+ inv_map={0:"axisymmetric",1:"sheet_metal",2:"alloy_plate",3:"complex_plastic"}
355
+ df_gen['shape']= df_gen['shape_code'].map(inv_map).fillna('unknown')
356
 
357
  st.subheader("Configurazioni Generate (VAE)")
358
  st.dataframe(df_gen.round(2))
359
 
360
+ # Aggiungiamo GenAI test
361
+ st.markdown("### Suggerimenti Testuali Upcycling (distilgpt2)")
362
+ text_generator = pipeline("text-generation",
363
+ model="distilgpt2",
364
  device=0 if torch.cuda.is_available() else -1)
365
 
366
  def gen_upcycle_text(shape, thick, wei):
367
+ prompt = (
368
+ f"Ho un componente EoL con forma {shape}, spessore {thick:.1f} mm, peso {wei:.1f} kg.\n"
369
  "Dammi un'idea creativa di upcycling in italiano, con passaggi principali:"
370
  )
371
  out= text_generator(prompt, max_new_tokens=50, do_sample=True, top_k=50)
372
  return out[0]["generated_text"]
373
 
374
  for i, row in df_gen.iterrows():
375
+ sh= row.get("shape","unknown")
376
+ tk= row.get("thickness",1.0)
377
+ we= row.get("weight",10.0)
378
+ text_sugg= gen_upcycle_text(sh, tk, we)
379
+ st.write(f"**Idea {i+1}**: shape={sh}, thickness={tk:.1f}, weight={we:.1f}")
380
+ st.info(text_sugg)
381
  st.markdown("---")
382
 
383
  except Exception as e:
384
  st.error(f"Errore decodifica VAE: {e}")
385
 
386
+ ##################################################
387
  # DASHBOARD
388
+ ##################################################
389
  def show_dashboard():
390
  st.header("Dashboard")
391
  data= st.session_state.get("data",None)
392
  if data is None:
393
  st.error("Nessun dataset.")
394
  return
395
+ st.write("Distribuzione classi Target:", data["Target"].value_counts())
396
 
397
+ if "ml_results" in st.session_state:
398
  st.subheader("Risultati ML")
399
  st.dataframe(st.session_state["ml_results"])
400
  else:
401
+ st.info("Nessun risultato ML")
402
 
403
  if st.session_state.get("vae_trained",False):
404
  st.success("VAE addestrato.")
405
  else:
406
  st.warning("VAE non addestrato.")
407
 
408
+ ##################################################
409
  # HELP
410
+ ##################################################
411
  def show_help():
412
+ st.header("ℹ️ Guida Quattro Step")
413
  st.markdown("""
 
414
  1. **Step 1: Dataset**
415
+ Generi o carichi CSV, definisci compatibilità, e assegni 'Riutilizzo' vs 'Upcycling'.
416
 
417
  2. **Step 2: Addestramento ML**
418
+ Allena modelli (RandomForest, ecc.) su [Riutilizzo vs Upcycling].
419
 
420
  3. **Step 2B: Training VAE**
421
+ Allena VAE sulle feature geometriche (length, width, weight, thickness, shape_code).
422
 
423
  4. **Step 3: Upcycling Generative**
424
+ Genera N configurazioni col VAE e, per ognuna, ottieni un testo creativo di upcycling con un modello HF (distilgpt2).
425
 
426
+ **Dashboard**: metriche.
427
+ **Reset**: pulsante nella sidebar che cancella lo state.
428
  """)
429
 
430
+ ##################################################
431
  # RESET
432
+ ##################################################
433
  def reset_app():
434
  for k in ["data","models","ml_results","vae","vae_trained","vae_scaler","data_source","params_dim"]:
435
  if k in st.session_state:
 
437
  st.success("App resettata.")
438
  st.experimental_rerun()
439
 
440
+ ##################################################
441
  # MAIN
442
+ ##################################################
443
  def main():
444
+ st.sidebar.title("WEEKO 4 Step Flow")
445
+ step= st.sidebar.radio("Fasi:",[
446
  "Step 1: Dataset",
447
  "Step 2: Addestramento ML",
448
  "Step 2B: Training VAE",