proofyx / training /train_efficientnet_texture.py
Muhammed Sayeedur Rahman
feat: Add production infrastructure, CI/CD, Docker, tests, and implementation plan
6f2de72
Raw
History Blame Contribute Delete
4.67 kB
"""
Train EfficientNet-B4 texture branch on face-aligned portrait crops.
Captures texture artifacts (skin smoothness, hair detail, boundary artifacts)
that complement ViT's structural analysis.
Dataset: Multi-source portrait dataset (GAN + diffusion + real)
Output: models/efficient.pth
Usage:
python training/train_efficientnet_texture.py
"""
import sys
import os
from tqdm import tqdm
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if ROOT_DIR not in sys.path:
sys.path.insert(0, ROOT_DIR)
os.environ["HF_HOME"] = os.path.join(ROOT_DIR, ".hf_cache")
os.environ["HF_DATASETS_CACHE"] = os.path.join(ROOT_DIR, ".hf_cache", "datasets")
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torch.optim.lr_scheduler import CosineAnnealingLR
from core_models.efficientnet_texture import EfficientNetTexture
from training.dataset_portraits import (
load_portrait_dataset, PortraitDataset,
TRAIN_TRANSFORM, VAL_TRANSFORM,
)
# -------- CONFIG --------
BATCH_SIZE = 8 # B4 is large, keep batch small for CPU
EPOCHS = 15
BACKBONE_LR = 5e-6
HEAD_LR = 5e-4
MAX_SAMPLES = 20000
TRAIN_SPLIT = 0.85
MODEL_PATH = "models/efficient.pth"
EARLY_STOPPING_PATIENCE = 4
LABEL_SMOOTHING = 0.05
# -------------------------
def main():
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Device: {device}")
# -------- Dataset --------
print("\nLoading portrait dataset for texture training...")
train_data, val_data = load_portrait_dataset(
max_samples=MAX_SAMPLES,
train_split=TRAIN_SPLIT,
face_align=True,
)
train_loader = DataLoader(
PortraitDataset(train_data, TRAIN_TRANSFORM),
batch_size=BATCH_SIZE, shuffle=True, num_workers=0,
)
val_loader = DataLoader(
PortraitDataset(val_data, VAL_TRANSFORM),
batch_size=BATCH_SIZE, shuffle=False, num_workers=0,
)
# -------- Model --------
model = EfficientNetTexture().to(device)
criterion = nn.BCELoss()
# Differential LR: frozen early layers won't update anyway
trainable_backbone = [p for p in model.features.parameters() if p.requires_grad]
head_params = list(model.classifier.parameters())
optimizer = torch.optim.AdamW([
{"params": trainable_backbone, "lr": BACKBONE_LR, "weight_decay": 1e-4},
{"params": head_params, "lr": HEAD_LR, "weight_decay": 1e-4},
])
scheduler = CosineAnnealingLR(optimizer, T_max=EPOCHS, eta_min=1e-7)
# -------- Training --------
print(f"\nTraining EfficientNet-B4 Texture (label_smoothing={LABEL_SMOOTHING})...\n")
best_val_loss = float("inf")
patience = 0
for epoch in range(EPOCHS):
model.train()
total_loss = 0.0
for imgs, labels in tqdm(train_loader, desc=f"Epoch {epoch+1}/{EPOCHS}"):
imgs = imgs.to(device)
labels = labels.unsqueeze(1).to(device)
smoothed = labels * (1 - LABEL_SMOOTHING) + (1 - labels) * LABEL_SMOOTHING
preds = model(imgs)
loss = criterion(preds, smoothed)
optimizer.zero_grad()
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(train_loader)
# -------- Validation --------
model.eval()
val_loss = 0.0
correct, total = 0, 0
with torch.no_grad():
for imgs, labels in val_loader:
imgs = imgs.to(device)
labels_t = labels.unsqueeze(1).to(device)
preds = model(imgs)
val_loss += criterion(preds, labels_t).item()
correct += ((preds > 0.5).float() == labels_t).sum().item()
total += labels.size(0)
avg_val = val_loss / len(val_loader)
val_acc = correct / total
scheduler.step()
print(f"Epoch [{epoch+1}/{EPOCHS}] "
f"| Train: {avg_loss:.4f} | Val: {avg_val:.4f} | Acc: {val_acc:.4f}")
if avg_val < best_val_loss:
best_val_loss = avg_val
patience = 0
os.makedirs("models", exist_ok=True)
torch.save(model.state_dict(), MODEL_PATH)
print(f" -> Best model saved (val_loss={avg_val:.4f})")
else:
patience += 1
if patience >= EARLY_STOPPING_PATIENCE:
print(f"\nEarly stopping at epoch {epoch+1}")
break
print(f"\nEfficientNet-B4 Texture trained. Saved to: {MODEL_PATH}")
if __name__ == "__main__":
main()