This commit is contained in:
Leon Astner 2025-08-02 00:45:58 +02:00
parent 922ad3a78c
commit e8cc39387f
7 changed files with 210 additions and 54 deletions

Binary file not shown.

View file

@ -30,7 +30,7 @@ class MyApp extends StatelessWidget {
// tested with just a hot reload. // tested with just a hot reload.
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
), ),
home: const MyHomePage(title: 'S'), home: const MyHomePage(title: 'Now Playing'),
); );
} }
} }

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 481 KiB

After

Width:  |  Height:  |  Size: 436 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 256 KiB

Before After
Before After

View file

@ -1,115 +1,271 @@
#!/usr/bin/env python3
# scripts/train.py # scripts/train.py
""" """
Script di training per il classificatore basilico vs pomodoro. Script di training per il classificatore basilico vs pomodoro.
Struttura: Funzionalità:
- carica dataset da data/basil_tomato/train e /val - Carica dataset da data/basil_tomato/train e /val
- transfer learning con EfficientNet-B0 - Transfer learning con EfficientNet-B0
- salva il miglior modello in models/basil_tomato_classifier.pth - Salva il miglior modello in models/basil_tomato_classifier.pth
""" """
import os import os
import sys
import torch import torch
import torch.nn as nn import torch.nn as nn
import torch.optim as optim import torch.optim as optim
from torchvision import datasets, transforms, models from torchvision import datasets, transforms
from torchvision.models import efficientnet_b0, EfficientNet_B0_Weights
from torch.utils.data import DataLoader from torch.utils.data import DataLoader
from pathlib import Path
# 1) Percorsi dataset # 1) Percorsi dataset (usa percorsi assoluti per sicurezza)
train_dir = "data/basil_tomato/train" script_dir = Path(__file__).parent
val_dir = "data/basil_tomato/val" base_dir = script_dir.parent if script_dir.parent.name != "scripts" else script_dir
train_dir = base_dir / "scripts" / "data" / "basil_tomato" / "train"
val_dir = base_dir / "scripts" / "data" / "basil_tomato" / "val"
models_dir = base_dir / "scripts" / "models"
# 2) Trasformazioni dati print(f"🔍 Cercando dataset in:")
print(f" Train: {train_dir}")
print(f" Val: {val_dir}")
print(f" Models: {models_dir}")
# Verifica esistenza directory
if not train_dir.exists():
print(f"❌ Directory train non trovata: {train_dir}")
sys.exit(1)
if not val_dir.exists():
print(f"❌ Directory validation non trovata: {val_dir}")
sys.exit(1)
# 2) Valori standard di normalizzazione ImageNet
IMGNET_MEAN = [0.485, 0.456, 0.406]
IMGNET_STD = [0.229, 0.224, 0.225]
# 3) Trasformazioni dati
train_transforms = transforms.Compose([ train_transforms = transforms.Compose([
transforms.RandomResizedCrop(224), transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(), transforms.RandomHorizontalFlip(),
transforms.ToTensor(), transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) transforms.Normalize(IMGNET_MEAN, IMGNET_STD)
]) ])
val_transforms = transforms.Compose([ val_transforms = transforms.Compose([
transforms.Resize(256), transforms.Resize(256),
transforms.CenterCrop(224), transforms.CenterCrop(224),
transforms.ToTensor(), transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) transforms.Normalize(IMGNET_MEAN, IMGNET_STD)
]) ])
# 3) Crea dataset e DataLoader # 4) Dataset e DataLoader con error handling
train_ds = datasets.ImageFolder(train_dir, transform=train_transforms) try:
val_ds = datasets.ImageFolder(val_dir, transform=val_transforms) train_ds = datasets.ImageFolder(str(train_dir), transform=train_transforms)
val_ds = datasets.ImageFolder(str(val_dir), transform=val_transforms)
if len(train_ds) == 0:
print(f"❌ Nessuna immagine trovata in {train_dir}")
sys.exit(1)
if len(val_ds) == 0:
print(f"❌ Nessuna immagine trovata in {val_dir}")
sys.exit(1)
except Exception as e:
print(f"❌ Errore nel caricamento dataset: {e}")
sys.exit(1)
train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) # Ottimizza batch size per GPU disponibile
val_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=4)
print(f"Classi trovate: {train_ds.classes}")
print(f"Numero immagini train: {len(train_ds)}, validation: {len(val_ds)}")
# 4) Configura device
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
if torch.cuda.is_available():
gpu_memory = torch.cuda.get_device_properties(0).total_memory / 1024**3
batch_size = 32 if gpu_memory > 6 else 16
num_workers = min(4, os.cpu_count() or 1)
print(f"🚀 GPU: {torch.cuda.get_device_name(0)} ({gpu_memory:.1f} GB)")
print(f"⚙️ Batch size ottimizzato: {batch_size}")
else:
batch_size = 8
num_workers = min(2, os.cpu_count() or 1)
print("💻 Usando CPU")
# 5) Costruisci il modello train_loader = DataLoader(
model = models.efficientnet_b0(pretrained=True) train_ds, batch_size=batch_size, shuffle=True,
num_classes = len(train_ds.classes) num_workers=num_workers, pin_memory=torch.cuda.is_available()
model.classifier[1] = nn.Linear(model.classifier[1].in_features, num_classes) )
model = model.to(device) val_loader = DataLoader(
val_ds, batch_size=batch_size, shuffle=False,
num_workers=num_workers, pin_memory=torch.cuda.is_available()
)
# 6) Definisci criterio e ottimizzatore print(f"✅ Classi trovate: {train_ds.classes}")
print(f"📊 Numero immagini - Train: {len(train_ds)}, Validation: {len(val_ds)}")
# Verifica bilanciamento classi
class_counts_train = {}
class_counts_val = {}
for idx, (_, label) in enumerate(train_ds):
class_name = train_ds.classes[label]
class_counts_train[class_name] = class_counts_train.get(class_name, 0) + 1
for idx, (_, label) in enumerate(val_ds):
class_name = val_ds.classes[label]
class_counts_val[class_name] = class_counts_val.get(class_name, 0) + 1
print(f"📈 Distribuzione train: {class_counts_train}")
print(f"📈 Distribuzione val: {class_counts_val}")
# 5) Configura device (già fatto sopra)
# 6) Costruisci il modello con pesi pre-addestrati (fix deprecation warning)
print("🔄 Caricando EfficientNet-B0 con pesi pre-addestrati...")
try:
model = efficientnet_b0(weights=EfficientNet_B0_Weights.IMAGENET1K_V1)
num_classes = len(train_ds.classes)
# Sostituisci il classificatore finale
model.classifier[1] = nn.Linear(
model.classifier[1].in_features,
num_classes
)
model = model.to(device)
# Ottimizzazioni per GPU
if torch.cuda.is_available():
model = model.half() # Usa mixed precision per risparmiare memoria
print("✅ Mixed precision attivata")
print(f"✅ Modello caricato con {num_classes} classi: {train_ds.classes}")
except Exception as e:
print(f"❌ Errore nel caricamento del modello: {e}")
sys.exit(1)
# 7) Criterio e ottimizzatore
criterion = nn.CrossEntropyLoss() criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-5) optimizer = optim.Adam(
model.parameters(), lr=1e-4, weight_decay=1e-5
# 7) Funzioni di training e validation )
# 8) Funzione di training per un'epoca con progress tracking
def train_epoch(): def train_epoch():
model.train() model.train()
running_loss, running_corrects = 0.0, 0 running_loss, running_corrects = 0.0, 0
total_batches = len(train_loader)
for inputs, labels in train_loader: for batch_idx, (inputs, labels) in enumerate(train_loader):
inputs, labels = inputs.to(device), labels.to(device) inputs, labels = inputs.to(device), labels.to(device)
# Mixed precision per GPU
if torch.cuda.is_available():
inputs = inputs.half()
optimizer.zero_grad() optimizer.zero_grad()
outputs = model(inputs) outputs = model(inputs)
loss = criterion(outputs, labels) loss = criterion(outputs, labels)
loss.backward() loss.backward()
optimizer.step() optimizer.step()
running_loss += loss.item() * inputs.size(0) running_loss += loss.item() * inputs.size(0)
running_corrects += (outputs.argmax(1) == labels).sum().item() running_corrects += (outputs.argmax(1) == labels).sum().item()
# Progress tracking
if (batch_idx + 1) % max(1, total_batches // 10) == 0:
progress = (batch_idx + 1) / total_batches * 100
print(f" 📈 Training progress: {progress:.1f}% ({batch_idx + 1}/{total_batches})")
epoch_loss = running_loss / len(train_ds) epoch_loss = running_loss / len(train_ds)
epoch_acc = running_corrects / len(train_ds) epoch_acc = running_corrects / len(train_ds)
return epoch_loss, epoch_acc return epoch_loss, epoch_acc
# 9) Funzione di validazione con progress tracking
def validate_epoch(): def validate_epoch():
model.eval() model.eval()
val_loss, val_corrects = 0.0, 0 val_loss, val_corrects = 0.0, 0
total_batches = len(val_loader)
with torch.no_grad(): with torch.no_grad():
for inputs, labels in val_loader: for batch_idx, (inputs, labels) in enumerate(val_loader):
inputs, labels = inputs.to(device), labels.to(device) inputs, labels = inputs.to(device), labels.to(device)
# Mixed precision per GPU
if torch.cuda.is_available():
inputs = inputs.half()
outputs = model(inputs) outputs = model(inputs)
loss = criterion(outputs, labels) loss = criterion(outputs, labels)
val_loss += loss.item() * inputs.size(0) val_loss += loss.item() * inputs.size(0)
val_corrects += (outputs.argmax(1) == labels).sum().item() val_corrects += (outputs.argmax(1) == labels).sum().item()
# Progress tracking
if (batch_idx + 1) % max(1, total_batches // 5) == 0:
progress = (batch_idx + 1) / total_batches * 100
print(f" 📊 Validation progress: {progress:.1f}% ({batch_idx + 1}/{total_batches})")
loss = val_loss / len(val_ds) loss = val_loss / len(val_ds)
acc = val_corrects / len(val_ds) acc = val_corrects / len(val_ds)
return loss, acc return loss, acc
# 8) Training loop principale # 10) Loop di training principale con miglioramenti
best_val_acc = 0.0 if __name__ == "__main__":
os.makedirs("models", exist_ok=True) import time
best_val_acc = 0.0
models_dir.mkdir(exist_ok=True)
print(f"\n🚀 Iniziando training per {10} epoche...")
print(f"💾 I modelli saranno salvati in: {models_dir}")
start_time = time.time()
for epoch in range(1, 11): # 10 epoche for epoch in range(1, 11): # 10 epoche
train_loss, train_acc = train_epoch() epoch_start = time.time()
val_loss, val_acc = validate_epoch() print(f"\n🔄 Epoca {epoch}/10:")
# Training
print(" 🏋️ Training...")
train_loss, train_acc = train_epoch()
# Validation
print(" 🔍 Validation...")
val_loss, val_acc = validate_epoch()
epoch_time = time.time() - epoch_start
print(f"Epoca {epoch}: train_loss={train_loss:.4f}, train_acc={train_acc:.4f} | " print(
f"val_loss={val_loss:.4f}, val_acc={val_acc:.4f}") f"✅ Epoca {epoch}: train_loss={train_loss:.4f}, "
f"train_acc={train_acc:.4f} | val_loss={val_loss:.4f}, "
f"val_acc={val_acc:.4f} | tempo={epoch_time:.1f}s"
)
# Salva il modello migliore # Salva il miglior modello con validazione
if val_acc > best_val_acc: if val_acc > best_val_acc:
best_val_acc = val_acc best_val_acc = val_acc
save_path = os.path.join("models", "basil_tomato_classifier.pth") save_path = models_dir / "basil_tomato_classifier.pth"
torch.save(model.state_dict(), save_path)
print(f"--> Nuovo best model salvato con val_acc={val_acc:.4f}") try:
# Salva sia state_dict che modello completo
torch.save({
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'best_val_acc': best_val_acc,
'train_acc': train_acc,
'val_loss': val_loss,
'classes': train_ds.classes,
'num_classes': num_classes
}, save_path)
print(f"💾 Nuovo best model salvato con val_acc={val_acc:.4f}")
except Exception as e:
print(f"❌ Errore nel salvataggio: {e}")
# Cleanup GPU memory
if torch.cuda.is_available():
torch.cuda.empty_cache()
print("Training completato. Best val_acc: {:.4f}".format(best_val_acc)) total_time = time.time() - start_time
print(f"\n🎉 Training completato! Best val_acc: {best_val_acc:.4f}")
print(f"⏱️ Tempo totale: {total_time:.1f}s ({total_time/60:.1f} minuti)")
# Statistiche finali
if torch.cuda.is_available():
print(f"📊 Memoria GPU utilizzata: {torch.cuda.max_memory_allocated()/1024**3:.2f} GB")
torch.cuda.reset_peak_memory_stats()