Spaces:
Sleeping
Sleeping
File size: 7,655 Bytes
1970a5b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | """
Training Script β IEEE Research Experiments
Trains HybridDeepfakeDetector on FaceForensics++ or Celeb-DF v2
Usage:
python train.py --data_dir /path/to/dataset --epochs 30 --batch_size 32
"""
import argparse, time, os
try:
from tqdm import tqdm
except ImportError:
tqdm = None
from pathlib import Path
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader, WeightedRandomSampler
import torchvision.transforms as T
from PIL import Image
from sklearn.metrics import roc_auc_score
from model import HybridDeepfakeDetector
# ββ Dataset βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class DeepfakeDataset(Dataset):
"""
Expects directory layout:
data_dir/
real/ β real face crops (PNG/JPG)
fake/ β fake face crops
"""
TRAIN_TF = T.Compose([
T.Resize((224, 224)),
T.RandomHorizontalFlip(),
T.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.1),
T.RandomRotation(10),
T.GaussianBlur(kernel_size=3, sigma=(0.1, 1.5)),
T.ToTensor(),
T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
VAL_TF = T.Compose([
T.Resize((224, 224)),
T.ToTensor(),
T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
def __init__(self, data_dir: str, split: str = "train"):
self.tf = self.TRAIN_TF if split == "train" else self.VAL_TF
self.samples = []
for label, folder in [(0, "real"), (1, "fake")]:
p = Path(data_dir) / folder
if p.exists():
for img in p.rglob("*.jpg"):
self.samples.append((str(img), label))
for img in p.rglob("*.png"):
self.samples.append((str(img), label))
if not self.samples:
raise ValueError(f"No images found in {data_dir}. "
f"Ensure real/ and fake/ sub-directories exist under {data_dir}.")
def __len__(self):
return len(self.samples)
def __getitem__(self, idx):
path, label = self.samples[idx]
img = Image.open(path).convert("RGB")
return self.tf(img), torch.tensor(label, dtype=torch.float32)
def get_sampler(dataset: DeepfakeDataset) -> WeightedRandomSampler:
labels = [s[1] for s in dataset.samples]
counts = [labels.count(0), labels.count(1)]
weights = [1.0 / counts[l] for l in labels]
return WeightedRandomSampler(weights, len(weights))
# ββ Training loop βββββββββββββββββββββββββββββββββββββββββββββββββββ
def train_epoch(model, loader, optimizer, criterion, device, scaler):
model.train()
total_loss, n = 0.0, 0
total_batches = len(loader)
iterator = tqdm(loader, desc=" Training", unit="batch",
bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}] loss={postfix}") \
if tqdm else loader
for batch_idx, (imgs, labels) in enumerate(iterator):
imgs, labels = imgs.to(device), labels.to(device)
optimizer.zero_grad()
with torch.amp.autocast(device_type=device, enabled=scaler is not None):
logits = model(imgs).squeeze(1)
loss = criterion(logits, labels)
if scaler:
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
else:
loss.backward()
optimizer.step()
total_loss += loss.item() * imgs.size(0)
n += imgs.size(0)
# Update tqdm with current avg loss
if tqdm and hasattr(iterator, 'set_postfix_str'):
iterator.set_postfix_str(f"{total_loss/n:.4f}")
elif not tqdm and (batch_idx % 50 == 0 or batch_idx == total_batches - 1):
pct = (batch_idx + 1) / total_batches * 100
print(f" Batch {batch_idx+1}/{total_batches} ({pct:.0f}%) avg_loss={total_loss/n:.4f}",
flush=True)
return total_loss / n
@torch.no_grad()
def evaluate(model, loader, device):
model.eval()
all_probs, all_labels = [], []
for imgs, labels in loader:
imgs = imgs.to(device)
probs = torch.sigmoid(model(imgs).squeeze(1)).cpu().numpy()
all_probs.extend(probs)
all_labels.extend(labels.numpy())
auc = roc_auc_score(all_labels, all_probs)
preds = [1 if p >= 0.5 else 0 for p in all_probs]
acc = np.mean(np.array(preds) == np.array(all_labels))
return {"auc": auc, "acc": acc}
# ββ Main βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--data_dir", default="../datasets/video_crops")
parser.add_argument("--epochs", type=int, default=30)
parser.add_argument("--batch_size", type=int, default=32)
parser.add_argument("--lr", type=float, default=1e-4)
parser.add_argument("--save_dir", default="models")
parser.add_argument("--device", default=None)
args = parser.parse_args()
device = args.device or ("cuda" if torch.cuda.is_available() else "cpu")
print(f"[Train] Device: {device}")
# Datasets
# data_dir should be the base data/ folder; train/val subdirs are appended internally
train_split_dir = str(Path(args.data_dir) / "train")
val_split_dir = str(Path(args.data_dir) / "val")
train_ds = DeepfakeDataset(train_split_dir, "train")
val_ds = DeepfakeDataset(val_split_dir, "val")
sampler = get_sampler(train_ds)
# num_workers=0 avoids Windows multiprocessing issues
train_dl = DataLoader(train_ds, batch_size=args.batch_size, sampler=sampler, num_workers=0, pin_memory=False, drop_last=True)
val_dl = DataLoader(val_ds, batch_size=args.batch_size, shuffle=False, num_workers=0)
print(f"[Train] Train samples: {len(train_ds)} | Val samples: {len(val_ds)}")
# Model
model = HybridDeepfakeDetector(pretrained=True).to(device)
# Loss with label smoothing
criterion = nn.BCEWithLogitsLoss(label_smoothing=0.1) if hasattr(
nn.BCEWithLogitsLoss, "label_smoothing"
) else nn.BCEWithLogitsLoss()
optimizer = optim.AdamW(model.parameters(), lr=args.lr, weight_decay=1e-4)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=args.epochs)
scaler = torch.cuda.amp.GradScaler() if device == "cuda" else None
os.makedirs(args.save_dir, exist_ok=True)
best_auc = 0.0
for epoch in range(1, args.epochs + 1):
t0 = time.time()
loss = train_epoch(model, train_dl, optimizer, criterion, device, scaler)
mets = evaluate(model, val_dl, device)
scheduler.step()
print(f"Epoch {epoch:03d}/{args.epochs} "
f"Loss={loss:.4f} AUC={mets['auc']*100:.2f}% "
f"ACC={mets['acc']*100:.2f}% "
f"[{time.time()-t0:.1f}s]")
if mets["auc"] > best_auc:
best_auc = mets["auc"]
save_path = Path(args.save_dir) / "deepfake_model.pth"
torch.save(model.state_dict(), save_path)
print(f" β Best model saved β {save_path} (AUC={best_auc*100:.2f}%)")
print(f"\n[Done] Best AUC: {best_auc*100:.2f}%")
if __name__ == "__main__":
main()
|