Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -20,17 +20,42 @@ import torch
|
|
| 20 |
import torch.nn as nn
|
| 21 |
import torch.nn.functional as F
|
| 22 |
|
| 23 |
-
#
|
| 24 |
from transformers import pipeline
|
| 25 |
|
| 26 |
-
|
| 27 |
-
#
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 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
|
| 86 |
return mse+kld
|
| 87 |
|
| 88 |
-
|
| 89 |
-
#
|
| 90 |
-
|
| 91 |
SHAPE_MAPPING = {"axisymmetric":0,"sheet_metal":1,"alloy_plate":2,"complex_plastic":3}
|
| 92 |
-
ML_FEATURES
|
| 93 |
-
VAE_FEATURES= ["length","width","weight","thickness","shape_code"]
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)<=
|
| 100 |
-
c_thi= abs(r["thickness"]-t_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 |
-
#
|
| 114 |
-
|
| 115 |
def step1_dataset():
|
| 116 |
st.header("Step 1: Dataset")
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
with
|
| 120 |
-
|
| 121 |
data=None
|
| 122 |
-
if
|
| 123 |
-
n= st.slider("Campioni sintetici",100,2000,300,step=
|
| 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
|
| 129 |
if upl:
|
| 130 |
df= pd.read_csv(upl)
|
| 131 |
-
|
| 132 |
-
if not all(c in df.columns for c in
|
| 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 |
-
|
| 142 |
-
|
| 143 |
-
st.
|
| 144 |
-
|
| 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 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
st.
|
| 154 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
"
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 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
|
| 181 |
data= st.session_state.get("data",None)
|
| 182 |
if data is None:
|
| 183 |
-
st.error("
|
| 184 |
return
|
| 185 |
if "Target" not in data.columns:
|
| 186 |
-
st.error("
|
| 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(
|
| 192 |
return
|
| 193 |
-
X= data[features_ml]
|
| 194 |
y= data["Target"].map({"Riutilizzo Funzionale":0,"Upcycling Creativo":1})
|
| 195 |
if len(y.unique())<2:
|
| 196 |
-
st.error("
|
| 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 |
-
|
| 203 |
-
trained_pipes={}
|
| 204 |
results=[]
|
| 205 |
-
for nome,
|
| 206 |
st.subheader(f"Modello: {nome}")
|
| 207 |
from sklearn.pipeline import Pipeline
|
| 208 |
pipe= Pipeline([
|
| 209 |
-
("scaler",
|
| 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
|
| 218 |
-
|
| 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
|
| 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"]=
|
| 236 |
st.session_state["ml_results"]= df_r
|
| 237 |
else:
|
| 238 |
-
st.error("Nessun modello addestrato
|
| 239 |
st.session_state["models"]=None
|
| 240 |
|
| 241 |
-
|
| 242 |
-
#
|
| 243 |
-
|
| 244 |
def step2b_trainVAE():
|
| 245 |
-
st.header("Step 2B: Training VAE
|
| 246 |
|
| 247 |
-
data=st.session_state.get("data",None)
|
| 248 |
if data is None:
|
| 249 |
-
st.error("
|
| 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("
|
| 259 |
-
lr= st.number_input("Learning Rate",1e-5,1e-2,1e-3,format="%e")
|
| 260 |
-
bs= st.selectbox("Batch size",[16,32,64],
|
| 261 |
|
| 262 |
if not st.session_state.get("vae_trained",False):
|
| 263 |
-
st.warning("VAE non
|
| 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"
|
| 295 |
st.line_chart(losses)
|
| 296 |
st.session_state["vae_trained"]= True
|
| 297 |
else:
|
| 298 |
-
st.success("VAE già addestrato.
|
|
|
|
|
|
|
|
|
|
| 299 |
|
| 300 |
-
|
| 301 |
# STEP 3: Upcycling Generative
|
| 302 |
-
|
| 303 |
def step3_upcycling_generative():
|
| 304 |
-
st.header("Step 3: Upcycling Generative
|
| 305 |
|
| 306 |
-
# Verifica se VAE c'è
|
| 307 |
if not st.session_state.get("vae_trained",False):
|
| 308 |
-
st.error("Devi addestrare il VAE
|
| 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("
|
| 314 |
return
|
| 315 |
|
| 316 |
-
|
|
|
|
| 317 |
|
| 318 |
-
|
| 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 |
-
|
| 327 |
-
z= torch.randn(n_ideas, lat_dim)
|
| 328 |
recon= vae.decode(z)
|
| 329 |
arr= recon.numpy()
|
| 330 |
try:
|
| 331 |
-
|
| 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 |
-
|
| 338 |
-
df_gen['shape']= df_gen['shape_code'].map(
|
| 339 |
|
| 340 |
st.subheader("Configurazioni Generate (VAE)")
|
| 341 |
st.dataframe(df_gen.round(2))
|
| 342 |
|
| 343 |
-
#
|
| 344 |
-
st.markdown("### Suggerimenti Testuali
|
| 345 |
-
text_generator = pipeline("text-generation",
|
|
|
|
| 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
|
| 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 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
st.write(f"**Idea {i+1}**: shape={
|
| 362 |
-
st.info(
|
| 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
|
| 378 |
|
| 379 |
-
if
|
| 380 |
st.subheader("Risultati ML")
|
| 381 |
st.dataframe(st.session_state["ml_results"])
|
| 382 |
else:
|
| 383 |
-
st.info("Nessun risultato ML
|
| 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
|
| 395 |
st.markdown("""
|
| 396 |
-
**Flusso**:
|
| 397 |
1. **Step 1: Dataset**
|
| 398 |
-
|
| 399 |
|
| 400 |
2. **Step 2: Addestramento ML**
|
| 401 |
-
|
| 402 |
|
| 403 |
3. **Step 2B: Training VAE**
|
| 404 |
-
|
| 405 |
|
| 406 |
4. **Step 3: Upcycling Generative**
|
| 407 |
-
|
| 408 |
|
| 409 |
-
**Dashboard**:
|
|
|
|
| 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
|
| 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",
|