atharvaballa commited on
Commit
dd652d7
Β·
1 Parent(s): f92c6c6

Add deepfake detection model and app files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *pth filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python virtual environment
2
+ venv/
3
+ .env/
4
+ __pycache__/
5
+ *.pyc
6
+
7
+ # OS / editor
8
+ .DS_Store
9
+ Thumbs.db
10
+ .vscode/
11
+
12
+ # Large Data & Models
13
+ data/
14
+ outputs/
15
+ *.pth
16
+
17
+ # Environment & Cache
18
+ .venv/
19
+ .ipynb_checkpoints/
app.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ # ---- IMPORT BACKENDS ----
4
+ from image_backend import predict_image_pil
5
+ # =========================
6
+
7
+
8
+ # =========================
9
+ # IMAGE LOGIC
10
+ # =========================
11
+ def analyze_image(image):
12
+ if image is None:
13
+ return "", "", "", None
14
+
15
+ label, confidence, heatmap = predict_image_pil(image)
16
+
17
+ if label == "Fake":
18
+ if confidence >= 90:
19
+ risk = '<span class="material-icons">error</span> High likelihood of deepfake'
20
+ elif confidence >= 60:
21
+ risk = '<span class="material-icons">warning</span> Possibly deepfake'
22
+ else:
23
+ risk = '<span class="material-icons">help_outline</span> Uncertain deepfake'
24
+ else:
25
+ if confidence >= 90:
26
+ risk = '<span class="material-icons">check_circle</span> Likely real'
27
+ elif confidence >= 60:
28
+ risk = '<span class="material-icons">warning</span> Possibly real'
29
+ else:
30
+ risk = '<span class="material-icons">help_outline</span> Uncertain – needs review'
31
+
32
+ return label, f"{confidence} %", risk, heatmap
33
+
34
+
35
+ # =========================
36
+ # UI
37
+ # =========================
38
+ with gr.Blocks() as demo:
39
+
40
+ # Load Material Icons
41
+ gr.Markdown("""
42
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
43
+ """)
44
+
45
+ gr.Markdown("# AI Driven Deepfake Detection System")
46
+
47
+ with gr.Tabs():
48
+
49
+ # =========================
50
+ # HOME TAB (RESTORED)
51
+ # =========================
52
+ with gr.Tab("Home"):
53
+ gr.Markdown("""
54
+ ## Welcome
55
+
56
+ This system detects AI-generated (deepfake) content in images using
57
+ transformer-based deep learning models.
58
+ """)
59
+
60
+ gr.Markdown("""
61
+ ### Supported inputs
62
+ - Images: JPG, PNG (face-centric images recommended)
63
+ """)
64
+
65
+ gr.Markdown("""
66
+ ### How to use
67
+ 1. Select a detection mode using the tabs above.
68
+ 2. Upload an image or audio file.
69
+ 3. Click **Submit** to start analysis.
70
+ 4. Review the prediction, confidence score, and risk assessment.
71
+ """)
72
+
73
+ gr.Markdown("""
74
+ ### Understanding the results
75
+ - **Prediction**: Model decision (Real / Fake)
76
+ - **Confidence**: Certainty percentage of the prediction
77
+ - **Risk Assessment**:
78
+ - High likelihood β†’ strong indication
79
+ - Possibly β†’ caution advised
80
+ - Uncertain β†’ manual review recommended
81
+ """)
82
+
83
+ gr.Markdown("""
84
+ ### Explainability
85
+ For images, attention heatmaps highlight the facial regions that influenced
86
+ the model’s decision, supporting transparency and forensic analysis.
87
+ """)
88
+
89
+ gr.Markdown("""
90
+ ### Data privacy & intended use
91
+ Uploaded files are processed temporarily and are not stored.
92
+ This system is intended as a decision-support tool and should not be used
93
+ as the sole source of verification.
94
+ """)
95
+
96
+ # =========================
97
+ # IMAGE TAB
98
+ # =========================
99
+ with gr.Tab("Image Deepfake"):
100
+ gr.Markdown("## Deepfake Image Detection")
101
+
102
+ with gr.Row():
103
+ with gr.Column(scale=1):
104
+ image_input = gr.Image(
105
+ label="Upload Image",
106
+ type="pil",
107
+ height=280
108
+ )
109
+ img_submit = gr.Button("Submit")
110
+ img_clear = gr.Button("Clear")
111
+
112
+ with gr.Column(scale=2):
113
+ img_pred = gr.Text(label="Prediction")
114
+ img_conf = gr.Text(label="Confidence")
115
+ img_risk = gr.HTML(label="Risk Assessment", value="")
116
+ img_heatmap = gr.Image(
117
+ label="Explainability Heatmap",
118
+ height=280
119
+ )
120
+
121
+ img_submit.click(
122
+ analyze_image,
123
+ image_input,
124
+ [img_pred, img_conf, img_risk, img_heatmap]
125
+ )
126
+
127
+ img_clear.click(
128
+ lambda: (None, "", "", "", None),
129
+ None,
130
+ [image_input, img_pred, img_conf, img_risk, img_heatmap]
131
+ )
132
+
133
+ demo.launch(css="style.css")
evaluate.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import torch
4
+ import numpy as np
5
+ import torch.nn.functional as F
6
+ import matplotlib.pyplot as plt
7
+
8
+ from torch.utils.data import DataLoader
9
+ from torchvision import datasets, transforms
10
+ from transformers import ViTForImageClassification, ViTConfig
11
+ from sklearn.metrics import (
12
+ accuracy_score,
13
+ precision_score,
14
+ recall_score,
15
+ f1_score,
16
+ roc_auc_score,
17
+ confusion_matrix,
18
+ roc_curve
19
+ )
20
+ from openpyxl import Workbook
21
+
22
+
23
+ def main():
24
+ # ----------------------------------
25
+ # PERFORMANCE TUNING
26
+ # ----------------------------------
27
+ torch.backends.cudnn.benchmark = True
28
+ torch.set_num_threads(8)
29
+
30
+ # ----------------------------------
31
+ # Device (SAFE)
32
+ # ----------------------------------
33
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
34
+ use_amp = device.type == "cuda"
35
+
36
+ if use_amp:
37
+ print(f"πŸš€ Using device: cuda ({torch.cuda.get_device_name(0)})")
38
+ else:
39
+ print("πŸš€ Using device: cpu")
40
+
41
+ # ----------------------------------
42
+ # Paths
43
+ # ----------------------------------
44
+ base_data_dir = "data"
45
+
46
+ # πŸ”΄ UPDATED MODEL PATH (NEW MODEL)
47
+ model_path = "model/vit_face_final_best.pth"
48
+
49
+ datasets_config = {
50
+ "FF++": f"{base_data_dir}/ff++/test",
51
+ "Celeb-DF": f"{base_data_dir}/celeb-df/test",
52
+ "DFDC": f"{base_data_dir}/dfdc/test",
53
+ }
54
+
55
+ os.makedirs("outputs", exist_ok=True)
56
+
57
+ # ----------------------------------
58
+ # Transforms
59
+ # ----------------------------------
60
+ test_tfms = transforms.Compose([
61
+ transforms.Resize(256),
62
+ transforms.CenterCrop(224),
63
+ transforms.ToTensor(),
64
+ transforms.Normalize(
65
+ mean=[0.485, 0.456, 0.406],
66
+ std=[0.229, 0.224, 0.225]
67
+ )
68
+ ])
69
+
70
+ # ----------------------------------
71
+ # Model
72
+ # ----------------------------------
73
+ config = ViTConfig.from_pretrained(
74
+ "google/vit-base-patch16-224",
75
+ num_labels=2
76
+ )
77
+
78
+ model = ViTForImageClassification.from_pretrained(
79
+ "google/vit-base-patch16-224",
80
+ config=config,
81
+ ignore_mismatched_sizes=True
82
+ )
83
+
84
+ model.load_state_dict(torch.load(model_path, map_location=device))
85
+ model.to(device)
86
+ model.eval()
87
+
88
+ print("βœ… Model loaded successfully")
89
+
90
+ # ----------------------------------
91
+ # Evaluation
92
+ # ----------------------------------
93
+ all_results = {}
94
+ print("\nπŸ§ͺ Running Cross-Dataset Evaluation...\n")
95
+
96
+ for ds_name, test_dir in datasets_config.items():
97
+ if not os.path.exists(test_dir):
98
+ print(f"⚠️ {ds_name}: path not found β€” skipping")
99
+ continue
100
+
101
+ test_ds = datasets.ImageFolder(test_dir, transform=test_tfms)
102
+
103
+ test_dl = DataLoader(
104
+ test_ds,
105
+ batch_size=32,
106
+ shuffle=False,
107
+ num_workers=2, # SAFE (inside main)
108
+ pin_memory=True
109
+ )
110
+
111
+ class_to_idx = test_ds.class_to_idx
112
+ real_idx = class_to_idx["real"]
113
+ fake_idx = class_to_idx["fake"]
114
+
115
+ print(f"πŸ“‚ {ds_name}: {len(test_ds)} samples | {class_to_idx}")
116
+
117
+ y_true, y_pred, y_probs = [], [], []
118
+ total_images = 0
119
+ total_time = 0.0
120
+
121
+ with torch.no_grad():
122
+ for imgs, labels in test_dl:
123
+ imgs = imgs.to(device, non_blocking=True)
124
+ labels = labels.to(device, non_blocking=True)
125
+
126
+ start = time.time()
127
+ with torch.amp.autocast(device_type="cuda", enabled=use_amp):
128
+ logits = model(imgs).logits
129
+ end = time.time()
130
+
131
+ probs = F.softmax(logits, dim=1)[:, fake_idx]
132
+ preds = torch.argmax(logits, dim=1)
133
+
134
+ total_time += (end - start)
135
+ total_images += imgs.size(0)
136
+
137
+ y_true.extend(labels.cpu().numpy())
138
+ y_pred.extend(preds.cpu().numpy())
139
+ y_probs.extend(probs.cpu().numpy())
140
+
141
+ # ---------------- Metrics ----------------
142
+ acc = accuracy_score(y_true, y_pred)
143
+ prec = precision_score(y_true, y_pred, zero_division=0)
144
+ rec = recall_score(y_true, y_pred, zero_division=0)
145
+ f1 = f1_score(y_true, y_pred, zero_division=0)
146
+
147
+ try:
148
+ auc = roc_auc_score(
149
+ (np.array(y_true) == fake_idx).astype(int),
150
+ y_probs
151
+ )
152
+ except ValueError:
153
+ auc = float("nan")
154
+
155
+ avg_time_ms = (total_time / total_images) * 1000
156
+ fps = total_images / total_time
157
+
158
+ all_results[ds_name] = {
159
+ "acc": acc,
160
+ "prec": prec,
161
+ "rec": rec,
162
+ "f1": f1,
163
+ "auc": auc,
164
+ "time": avg_time_ms,
165
+ "fps": fps,
166
+ }
167
+
168
+ print(
169
+ f"🎯 {ds_name} | Acc: {acc:.4f} | F1: {f1:.4f} | "
170
+ f"AUC: {auc:.4f} | {avg_time_ms:.2f} ms/img | {fps:.2f} FPS"
171
+ )
172
+
173
+ # ---------------- Confusion Matrix ----------------
174
+ cm = confusion_matrix(y_true, y_pred, labels=[real_idx, fake_idx])
175
+
176
+ plt.figure(figsize=(5, 4))
177
+ plt.imshow(cm)
178
+ plt.title(f"{ds_name} - Confusion Matrix")
179
+ plt.xlabel("Predicted")
180
+ plt.ylabel("True")
181
+ plt.xticks([0, 1], ["Real", "Fake"])
182
+ plt.yticks([0, 1], ["Real", "Fake"])
183
+
184
+ for i in range(2):
185
+ for j in range(2):
186
+ plt.text(j, i, cm[i, j], ha="center", va="center")
187
+
188
+ plt.tight_layout()
189
+ plt.savefig(f"outputs/cm_{ds_name}.png")
190
+ plt.close()
191
+
192
+ # ---------------- ROC Curve ----------------
193
+ fpr, tpr, _ = roc_curve(y_true, y_probs, pos_label=fake_idx)
194
+
195
+ plt.figure(figsize=(5, 4))
196
+ plt.plot(fpr, tpr, label=f"AUC = {auc:.4f}")
197
+ plt.plot([0, 1], [0, 1], linestyle="--")
198
+ plt.xlabel("False Positive Rate")
199
+ plt.ylabel("True Positive Rate")
200
+ plt.title(f"{ds_name} - ROC Curve")
201
+ plt.legend(loc="lower right")
202
+ plt.tight_layout()
203
+ plt.savefig(f"outputs/roc_{ds_name}.png")
204
+ plt.close()
205
+
206
+ # ----------------------------------
207
+ # EXPORT (EXCEL + TEXT)
208
+ # ----------------------------------
209
+ avg_metrics = {
210
+ "acc": np.mean([m["acc"] for m in all_results.values()]),
211
+ "prec": np.mean([m["prec"] for m in all_results.values()]),
212
+ "rec": np.mean([m["rec"] for m in all_results.values()]),
213
+ "f1": np.mean([m["f1"] for m in all_results.values()]),
214
+ "auc": np.nanmean([m["auc"] for m in all_results.values()]),
215
+ "time": np.mean([m["time"] for m in all_results.values()]),
216
+ "fps": np.mean([m["fps"] for m in all_results.values()]),
217
+ }
218
+
219
+ all_results["AVERAGE"] = avg_metrics
220
+
221
+ wb = Workbook()
222
+ ws = wb.active
223
+ ws.title = "Evaluation Results"
224
+ ws.append(["Dataset", "Accuracy", "Precision", "Recall", "F1", "AUC", "ms/img", "FPS"])
225
+
226
+ for ds, m in all_results.items():
227
+ ws.append([
228
+ ds,
229
+ round(m["acc"], 4),
230
+ round(m["prec"], 4),
231
+ round(m["rec"], 4),
232
+ round(m["f1"], 4),
233
+ round(m["auc"], 4),
234
+ round(m["time"], 2),
235
+ round(m["fps"], 2),
236
+ ])
237
+
238
+ wb.save("outputs/evaluation_results.xlsx")
239
+
240
+ with open("outputs/summary.txt", "w") as f:
241
+ for ds, m in all_results.items():
242
+ f.write(f"Dataset: {ds}\n")
243
+ for k, v in m.items():
244
+ f.write(f" {k.upper():<6}: {v}\n")
245
+ f.write("-" * 45 + "\n")
246
+
247
+ print("βœ… Evaluation complete (new model evaluated successfully)")
248
+
249
+ # ----------------------------------
250
+ # COMBINED ROC CURVE (IEEE-FRIENDLY)
251
+ # ----------------------------------
252
+ plt.figure(figsize=(5, 4))
253
+
254
+ for ds_name in datasets_config.keys():
255
+ if ds_name not in all_results:
256
+ continue
257
+
258
+ # Reload dataset to recompute ROC cleanly
259
+ test_dir = datasets_config[ds_name]
260
+ test_ds = datasets.ImageFolder(test_dir, transform=test_tfms)
261
+
262
+ class_to_idx = test_ds.class_to_idx
263
+ fake_idx = class_to_idx["fake"]
264
+
265
+ test_dl = DataLoader(
266
+ test_ds,
267
+ batch_size=32,
268
+ shuffle=False,
269
+ num_workers=2,
270
+ pin_memory=True
271
+ )
272
+
273
+ y_true, y_probs = [], []
274
+
275
+ with torch.no_grad():
276
+ for imgs, labels in test_dl:
277
+ imgs = imgs.to(device, non_blocking=True)
278
+ labels = labels.to(device, non_blocking=True)
279
+
280
+ with torch.amp.autocast(device_type="cuda", enabled=use_amp):
281
+ logits = model(imgs).logits
282
+
283
+ probs = F.softmax(logits, dim=1)[:, fake_idx]
284
+
285
+ y_true.extend((labels == fake_idx).cpu().numpy())
286
+ y_probs.extend(probs.cpu().numpy())
287
+
288
+ fpr, tpr, _ = roc_curve(y_true, y_probs)
289
+ auc_val = roc_auc_score(y_true, y_probs)
290
+
291
+ # Line styles for IEEE (print-safe)
292
+ if ds_name == "FF++":
293
+ style = "-"
294
+ elif ds_name == "Celeb-DF":
295
+ style = "--"
296
+ else: # DFDC
297
+ style = "-."
298
+
299
+ plt.plot(
300
+ fpr,
301
+ tpr,
302
+ linestyle=style,
303
+ linewidth=2,
304
+ label=f"{ds_name} (AUC = {auc_val:.4f})"
305
+ )
306
+
307
+ # Random baseline
308
+ plt.plot([0, 1], [0, 1], linestyle=":", linewidth=1)
309
+
310
+ plt.xlabel("False Positive Rate")
311
+ plt.ylabel("True Positive Rate")
312
+ plt.legend(loc="lower right")
313
+ plt.tight_layout()
314
+
315
+ # Save IEEE-ready figure
316
+ plt.savefig("outputs/roc_combined.png", dpi=300, bbox_inches="tight")
317
+ plt.savefig("outputs/roc_combined.pdf", dpi=300, bbox_inches="tight")
318
+ plt.close()
319
+
320
+
321
+ if __name__ == "__main__":
322
+ main()
image_backend.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torchvision import transforms
3
+ from transformers import ViTForImageClassification, ViTConfig
4
+ from PIL import Image
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+ import io
8
+ import os
9
+
10
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
11
+
12
+ config = ViTConfig.from_pretrained(
13
+ "google/vit-base-patch16-224",
14
+ num_labels=2,
15
+ output_attentions=True
16
+ )
17
+
18
+ model = ViTForImageClassification.from_pretrained(
19
+ "google/vit-base-patch16-224",
20
+ config=config,
21
+ ignore_mismatched_sizes=True
22
+ )
23
+
24
+ model.load_state_dict(
25
+ torch.load("model/vit_face_final_best.pth", map_location=device)
26
+ )
27
+
28
+ model.to(device)
29
+ model.eval()
30
+
31
+
32
+ transform = transforms.Compose([
33
+ transforms.Resize((224, 224)),
34
+ transforms.ToTensor(),
35
+ transforms.Normalize(
36
+ [0.485, 0.456, 0.406],
37
+ [0.229, 0.224, 0.225]
38
+ )
39
+ ])
40
+
41
+
42
+ def get_attention_map(model, img_tensor):
43
+ with torch.no_grad():
44
+ outputs = model(img_tensor, output_attentions=True)
45
+ attn = outputs.attentions[-1].mean(dim=1)[0]
46
+ cls_attn = attn[0, 1:]
47
+
48
+ grid = int(cls_attn.size(0) ** 0.5)
49
+ cls_attn = cls_attn.reshape(grid, grid).cpu().numpy()
50
+
51
+ cls_attn = (cls_attn - cls_attn.min()) / (cls_attn.max() - cls_attn.min())
52
+ return cls_attn
53
+
54
+
55
+ def overlay(image, heatmap):
56
+ heatmap = np.uint8(255 * heatmap)
57
+ heatmap = Image.fromarray(heatmap).resize(image.size)
58
+
59
+ fig, ax = plt.subplots(figsize=(4, 4))
60
+ ax.imshow(image)
61
+ ax.imshow(heatmap, cmap="jet", alpha=0.5)
62
+ ax.axis("off")
63
+
64
+ buf = io.BytesIO()
65
+ plt.savefig(buf, format="png", bbox_inches="tight", pad_inches=0)
66
+ plt.close(fig)
67
+ buf.seek(0)
68
+
69
+ return Image.open(buf)
70
+
71
+
72
+ def predict_image_pil(image):
73
+ image = image.convert("RGB")
74
+
75
+ x = transform(image).unsqueeze(0).to(device)
76
+
77
+ with torch.no_grad():
78
+ outputs = model(x)
79
+ logits = outputs.logits
80
+ pred = torch.argmax(logits, dim=1).item()
81
+
82
+ label = "Fake" if pred == 0 else "Real"
83
+
84
+ heat = get_attention_map(model, x)
85
+ heatmap_img = overlay(image, heat)
86
+
87
+ confidence = torch.softmax(logits, dim=1)[0][pred].item() * 100
88
+
89
+ return label, round(confidence, 2), heatmap_img
model/vit_face_final_best.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7ac6c4c0cc5b00ad79871e9e44d33472a4709b0ab5fe4e3990c9882434059a5c
3
+ size 343283314
model/vit_real_fake_best.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:296feef7a146ba07f374387161c5d04a7f043c2b7caef88fa71b97281a488f7d
3
+ size 343283529
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ torch
3
+ torchvision
4
+ transformers
5
+ tensorflow
6
+ librosa
7
+ opencv-python
8
+ matplotlib
9
+ pillow
10
+ numpy
11
+ scikit-learn
12
+ openpyxl
13
+ tqdm
style.css ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ .material-icons {
2
+ font-size: 22px;
3
+ color: #2c3e50; /* Professional dark tone */
4
+ vertical-align: middle;
5
+ margin-right: 6px;
6
+ }
train_vit_retinaface_earlystop.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import numpy as np
4
+ import torch
5
+ import torch.nn.functional as F
6
+ import torch.optim as optim
7
+
8
+ from torch.utils.data import DataLoader, ConcatDataset
9
+ from torchvision import datasets, transforms
10
+ from transformers import ViTForImageClassification, ViTConfig
11
+ from sklearn.utils.class_weight import compute_class_weight
12
+ from sklearn.metrics import confusion_matrix, classification_report
13
+ from tqdm import tqdm
14
+ from multiprocessing import freeze_support
15
+
16
+ # =======================================================
17
+ # Early Stopping
18
+ # =======================================================
19
+ class EarlyStopping:
20
+ def __init__(self, patience=3, min_delta=0.001):
21
+ self.patience = patience
22
+ self.min_delta = min_delta
23
+ self.best_loss = None
24
+ self.counter = 0
25
+ self.stop = False
26
+
27
+ def __call__(self, val_loss):
28
+ if self.best_loss is None:
29
+ self.best_loss = val_loss
30
+ return
31
+ if val_loss > self.best_loss - self.min_delta:
32
+ self.counter += 1
33
+ print(f"⏸ EarlyStopping {self.counter}/{self.patience}")
34
+ if self.counter >= self.patience:
35
+ self.stop = True
36
+ else:
37
+ self.best_loss = val_loss
38
+ self.counter = 0
39
+
40
+
41
+ def main():
42
+ # =======================================================
43
+ # Reproducibility
44
+ # =======================================================
45
+ SEED = 42
46
+ random.seed(SEED)
47
+ np.random.seed(SEED)
48
+ torch.manual_seed(SEED)
49
+ torch.cuda.manual_seed_all(SEED)
50
+
51
+ # =======================================================
52
+ # Device
53
+ # =======================================================
54
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
55
+ use_amp = device.type == "cuda"
56
+ print(f"πŸš€ Using device: {device}")
57
+
58
+ # =======================================================
59
+ # Dataset roots (YOUR STRUCTURE)
60
+ # =======================================================
61
+ DATA_DIR = "data"
62
+ DATASETS = {
63
+ "ff++": f"{DATA_DIR}/ff++",
64
+ "celeb-df": f"{DATA_DIR}/celeb-df",
65
+ "dfdc": f"{DATA_DIR}/dfdc",
66
+ }
67
+
68
+ # =======================================================
69
+ # STRONGER TRAIN AUGMENTATION (IMPORTANT)
70
+ # =======================================================
71
+ train_tfms = transforms.Compose([
72
+ transforms.Resize(256),
73
+ transforms.RandomResizedCrop(224, scale=(0.6, 1.0)),
74
+ transforms.RandomHorizontalFlip(),
75
+ transforms.RandomApply(
76
+ [transforms.ColorJitter(0.4, 0.4, 0.4, 0.1)],
77
+ p=0.8
78
+ ),
79
+ transforms.RandomGrayscale(p=0.2),
80
+ transforms.ToTensor(),
81
+ transforms.Normalize(
82
+ mean=[0.485, 0.456, 0.406],
83
+ std=[0.229, 0.224, 0.225]
84
+ )
85
+ ])
86
+
87
+ val_tfms = transforms.Compose([
88
+ transforms.Resize((224, 224)),
89
+ transforms.ToTensor(),
90
+ transforms.Normalize(
91
+ mean=[0.485, 0.456, 0.406],
92
+ std=[0.229, 0.224, 0.225]
93
+ )
94
+ ])
95
+
96
+ # =======================================================
97
+ # Load & CONCAT datasets
98
+ # =======================================================
99
+ train_sets, val_sets, test_sets = [], [], []
100
+
101
+ for name, base in DATASETS.items():
102
+ print(f"πŸ“‚ Loading {name}")
103
+ train_sets.append(datasets.ImageFolder(f"{base}/train", transform=train_tfms))
104
+ val_sets.append(datasets.ImageFolder(f"{base}/val", transform=val_tfms))
105
+ test_sets.append(datasets.ImageFolder(f"{base}/test", transform=val_tfms))
106
+
107
+ train_ds = ConcatDataset(train_sets)
108
+ val_ds = ConcatDataset(val_sets)
109
+ test_ds = ConcatDataset(test_sets)
110
+
111
+ # =======================================================
112
+ # DataLoaders (IMPROVED)
113
+ # =======================================================
114
+ train_dl = DataLoader(
115
+ train_ds, batch_size=32, shuffle=True,
116
+ num_workers=0, pin_memory=True
117
+ )
118
+ val_dl = DataLoader(
119
+ val_ds, batch_size=32, shuffle=False,
120
+ num_workers=0, pin_memory=True
121
+ )
122
+ test_dl = DataLoader(
123
+ test_ds, batch_size=32, shuffle=False,
124
+ num_workers=0, pin_memory=True
125
+ )
126
+
127
+ classes = ["fake", "real"]
128
+ print("Classes:", classes)
129
+ print(f"Train: {len(train_ds)} | Val: {len(val_ds)} | Test: {len(test_ds)}")
130
+
131
+ # =======================================================
132
+ # Class Weights (BALANCED ACROSS DATASETS)
133
+ # =======================================================
134
+ all_labels = []
135
+ for ds in train_sets:
136
+ all_labels.extend([label for _, label in ds.samples])
137
+
138
+ labels_np = np.array(all_labels)
139
+
140
+ class_weights = compute_class_weight(
141
+ class_weight="balanced",
142
+ classes=np.unique(labels_np),
143
+ y=labels_np
144
+ )
145
+
146
+ class_weights = torch.tensor(class_weights, dtype=torch.float).to(device)
147
+ print("Class weights:", class_weights)
148
+
149
+ # =======================================================
150
+ # Model
151
+ # =======================================================
152
+ config = ViTConfig.from_pretrained(
153
+ "google/vit-base-patch16-224",
154
+ num_labels=2
155
+ )
156
+
157
+ model = ViTForImageClassification.from_pretrained(
158
+ "google/vit-base-patch16-224",
159
+ config=config,
160
+ ignore_mismatched_sizes=True
161
+ ).to(device)
162
+
163
+ # Freeze backbone
164
+ for param in model.vit.parameters():
165
+ param.requires_grad = False
166
+
167
+ # Unfreeze LAST 2 transformer blocks
168
+ for block in model.vit.encoder.layer[-2:]:
169
+ for param in block.parameters():
170
+ param.requires_grad = True
171
+
172
+ print("βœ… Last 2 transformer blocks unfrozen")
173
+
174
+ # =======================================================
175
+ # Optimizer & Scheduler
176
+ # =======================================================
177
+ EPOCHS = 20
178
+ optimizer = optim.AdamW(model.parameters(), lr=3e-5, weight_decay=1e-4)
179
+ scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)
180
+ scaler = torch.cuda.amp.GradScaler(enabled=use_amp)
181
+ early_stopper = EarlyStopping(patience=3)
182
+
183
+ # =======================================================
184
+ # Training Loop
185
+ # =======================================================
186
+ best_val_loss = float("inf")
187
+
188
+ for epoch in range(EPOCHS):
189
+ print(f"\nEpoch {epoch+1}/{EPOCHS}")
190
+ model.train()
191
+
192
+ correct, total = 0, 0
193
+
194
+ for imgs, labels in tqdm(train_dl):
195
+ imgs, labels = imgs.to(device), labels.to(device)
196
+ optimizer.zero_grad()
197
+
198
+ with torch.amp.autocast(device_type="cuda", enabled=use_amp):
199
+ logits = model(imgs).logits
200
+ loss = F.cross_entropy(logits, labels, weight=class_weights)
201
+
202
+ scaler.scale(loss).backward()
203
+ scaler.step(optimizer)
204
+ scaler.update()
205
+
206
+ correct += (logits.argmax(1) == labels).sum().item()
207
+ total += labels.size(0)
208
+
209
+ train_acc = correct / total
210
+ print(f"Train Acc: {train_acc:.4f}")
211
+
212
+ # ---------------- Validation ----------------
213
+ model.eval()
214
+ val_loss, val_correct, val_total = 0.0, 0, 0
215
+
216
+ with torch.no_grad():
217
+ for imgs, labels in val_dl:
218
+ imgs, labels = imgs.to(device), labels.to(device)
219
+ with torch.amp.autocast(device_type="cuda", enabled=use_amp):
220
+ logits = model(imgs).logits
221
+ loss = F.cross_entropy(logits, labels, weight=class_weights)
222
+
223
+ val_loss += loss.item()
224
+ val_correct += (logits.argmax(1) == labels).sum().item()
225
+ val_total += labels.size(0)
226
+
227
+ val_loss /= len(val_dl)
228
+ val_acc = val_correct / val_total
229
+ print(f"Val Loss: {val_loss:.4f} | Val Acc: {val_acc:.4f}")
230
+
231
+ scheduler.step()
232
+ early_stopper(val_loss)
233
+
234
+ if val_loss < best_val_loss:
235
+ best_val_loss = val_loss
236
+ torch.save(model.state_dict(), "vit_face_final_best.pth")
237
+ print("βœ… Best model saved")
238
+
239
+ if early_stopper.stop:
240
+ print("πŸ›‘ Early stopping triggered")
241
+ break
242
+
243
+ # =======================================================
244
+ # Testing (AMP ENABLED)
245
+ # =======================================================
246
+ model.eval()
247
+ all_preds, all_labels = [], []
248
+
249
+ with torch.no_grad():
250
+ for imgs, labels in test_dl:
251
+ imgs = imgs.to(device)
252
+ with torch.amp.autocast(device_type="cuda", enabled=use_amp):
253
+ logits = model(imgs).logits
254
+
255
+ preds = logits.argmax(1).cpu().numpy()
256
+ all_preds.extend(preds)
257
+ all_labels.extend(labels.numpy())
258
+
259
+ print("\nπŸ“Š Confusion Matrix")
260
+ print(confusion_matrix(all_labels, all_preds))
261
+
262
+ print("\nπŸ“Š Classification Report")
263
+ print(classification_report(all_labels, all_preds, target_names=classes))
264
+
265
+ print("βœ… FINAL TRAINING COMPLETED SUCCESSFULLY")
266
+
267
+
268
+ if __name__ == "__main__":
269
+ freeze_support()
270
+ main()