Spaces:
Sleeping
π― Using the Trained Model β Real-Time Deepfake Interview Detection
This guide explains how to export your trained model, run it as a backend API, and connect it to a website or browser extension for real-time interview video analysis.
π Architecture Overview
Browser / Extension
β
β JPEG frame (base64) via WebSocket / HTTP POST
βΌ
βββββββββββββββββββββββββββ
β FastAPI Backend β β Loads your .pt model once at startup
β (Python, localhost or β β MTCNN detects face in each frame
β hosted server) β β InceptionResnetV1 classifies: REAL / FAKE
βββββββββββββββββββββββββββ
β
β JSON: { label, confidence, latency_ms }
βΌ
Website UI / Extension Overlay
STEP 1 β Export the Trained Model After Training
Run this in a new notebook cell after Cell 8 finishes training:
# ββ CELL: Export model for deployment ββββββββββββββββββββββββββββββ
import torch
# Load best weights into the model (already built in Cell 4)
model.load_state_dict(torch.load('models/best_model.pt', map_location='cpu'))
model.eval()
# Save as TorchScript (faster inference, no class definition needed at load time)
scripted = torch.jit.script(model)
scripted.save('models/deepfake_model_scripted.pt')
# Also save a plain state dict copy (easier to load in server)
torch.save(model.state_dict(), 'models/deepfake_model_weights.pt')
print("β
Exported: models/deepfake_model_scripted.pt")
print("β
Exported: models/deepfake_model_weights.pt")
Note: If TorchScript fails (InceptionResnetV1 has some dynamic ops), use the plain weights approach in the server below β it works just as well.
STEP 2 β Backend: FastAPI Inference Server
2a. Project structure
deepfake_main/
βββ models/
β βββ best_model.pt β your trained weights
β βββ deepfake_model_weights.pt
βββ server/
β βββ main.py β FastAPI app (create below)
β βββ requirements_server.txt
βββ Deepfake_Detection.ipynb
2b. Install server dependencies
pip install fastapi uvicorn[standard] python-multipart Pillow facenet-pytorch torch torchvision
Or create server/requirements_server.txt:
fastapi>=0.110.0
uvicorn[standard]>=0.29.0
python-multipart>=0.0.9
Pillow>=10.2.0
torch>=2.2.0
torchvision>=0.17.0
facenet-pytorch>=2.6.0
numpy>=1.26.0
2c. Create server/main.py
"""
Deepfake Detection API β real-time interview frame analysis
Run with: uvicorn main:app --host 0.0.0.0 --port 8000
"""
import io, base64, time
import torch
import torch.nn as nn
import numpy as np
from PIL import Image
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from torchvision import transforms
from facenet_pytorch import InceptionResnetV1, fixed_image_standardization, MTCNN
# ββ Config ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
WEIGHTS_PATH = "../models/best_model.pt" # adjust path if needed
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
IMG_SIZE = 299
CONFIDENCE_THRESHOLD = 0.60 # below this β "uncertain"
print(f"[SERVER] Device: {DEVICE}")
# ββ Model definition (must match Cell 4 exactly) ββββββββββββββββββ
class DeepfakeClassifier(nn.Module):
def __init__(self):
super().__init__()
self.backbone = InceptionResnetV1(
classify=False, pretrained='vggface2'
).to(DEVICE)
self.head = nn.Sequential(
nn.Linear(512, 256), nn.BatchNorm1d(256), nn.GELU(), nn.Dropout(0.4),
nn.Linear(256, 128), nn.BatchNorm1d(128), nn.GELU(), nn.Dropout(0.3),
nn.Linear(128, 2)
).to(DEVICE)
def forward(self, x):
return self.head(self.backbone(x))
# ββ Load model ββββββββββββββββββββββββββββββββββββββββββββββββββββ
model = DeepfakeClassifier()
model.load_state_dict(torch.load(WEIGHTS_PATH, map_location=DEVICE))
model.eval()
print(f"[SERVER] Model loaded from {WEIGHTS_PATH}")
# ββ MTCNN face detector βββββββββββββββββββββββββββββββββββββββββββ
mtcnn = MTCNN(
image_size=IMG_SIZE, keep_all=False, min_face_size=40,
device=DEVICE, post_process=False, margin=20
)
# ββ Transform (same as eval_transform in Cell 3) βββββββββββββββββ
transform = transforms.Compose([
transforms.Resize((IMG_SIZE, IMG_SIZE)),
transforms.ToTensor(),
fixed_image_standardization,
])
CLASS_NAMES = {0: "FAKE", 1: "REAL"}
# ββ FastAPI app βββββββββββββββββββββββββββββββββββββββββββββββββββ
app = FastAPI(title="Deepfake Detection API", version="1.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # restrict to your domain in production
allow_methods=["*"],
allow_headers=["*"],
)
class FrameRequest(BaseModel):
image_b64: str # base64-encoded JPEG/PNG frame from browser
class PredictionResponse(BaseModel):
label: str # "REAL" or "FAKE"
confidence: float # 0.0 β 1.0
uncertain: bool # True if below threshold
face_detected: bool
latency_ms: float
@app.get("/health")
def health():
return {"status": "ok", "device": str(DEVICE)}
@app.post("/predict", response_model=PredictionResponse)
def predict(req: FrameRequest):
t0 = time.perf_counter()
# 1. Decode base64 β PIL image
try:
img_bytes = base64.b64decode(req.image_b64)
img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
except Exception as e:
raise HTTPException(status_code=400, detail=f"Bad image: {e}")
# 2. Detect face with MTCNN
face_tensor = mtcnn(img)
if face_tensor is None:
return PredictionResponse(
label="UNKNOWN", confidence=0.0,
uncertain=True, face_detected=False,
latency_ms=round((time.perf_counter()-t0)*1000, 1)
)
# 3. Prepare tensor
face_pil = Image.fromarray(face_tensor.permute(1,2,0).byte().cpu().numpy())
inp = transform(face_pil).unsqueeze(0).to(DEVICE)
# 4. Inference
with torch.no_grad():
logits = model(inp)
probs = torch.softmax(logits, dim=1)[0]
pred = int(torch.argmax(probs).item())
conf = float(probs[pred].item())
label = CLASS_NAMES[pred]
latency = round((time.perf_counter() - t0) * 1000, 1)
return PredictionResponse(
label=label,
confidence=round(conf, 3),
uncertain=(conf < CONFIDENCE_THRESHOLD),
face_detected=True,
latency_ms=latency
)
2d. Run the server
# From the server/ directory:
cd server
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
Test it's alive:
http://localhost:8000/health β {"status":"ok","device":"cuda"}
http://localhost:8000/docs β Swagger UI for manual testing
STEP 3 β Frontend: Capture Webcam Frames and Send to API
Add this JavaScript to your website (works with any HTML page or React/Next.js app):
<!-- index.html β Minimal real-time deepfake detector UI -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Deepfake Interview Detector</title>
</head>
<body>
<video id="video" autoplay playsinline width="640" height="480"></video>
<canvas id="canvas" width="640" height="480" style="display:none"></canvas>
<div id="result">Waiting...</div>
<script>
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const result = document.getElementById('result');
const ctx = canvas.getContext('2d');
const API = 'http://localhost:8000/predict';
// Start webcam
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => { video.srcObject = stream; });
// Send a frame every 1 second (adjust as needed)
setInterval(async () => {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const b64 = canvas.toDataURL('image/jpeg', 0.8)
.replace('data:image/jpeg;base64,', '');
try {
const res = await fetch(API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ image_b64: b64 })
});
const data = await res.json();
const color = data.label === 'REAL' ? 'green' : 'red';
result.innerHTML = `
<span style="color:${color}; font-size:2em; font-weight:bold">
${data.label} (${(data.confidence * 100).toFixed(1)}%)
</span>
<br>Face: ${data.face_detected} | Latency: ${data.latency_ms}ms
${data.uncertain ? '<br>β οΈ Low confidence' : ''}
`;
} catch(e) {
result.textContent = 'API error: ' + e.message;
}
}, 1000);
</script>
</body>
</html>
STEP 4 β Browser Extension (for Google Meet / Zoom etc.)
A Chrome extension can overlay the prediction directly on top of a Google Meet or Zoom tab.
File structure
deepfake_extension/
βββ manifest.json
βββ content.js β injects into Google Meet page
βββ popup.html β extension popup (optional)
manifest.json
{
"manifest_version": 3,
"name": "Deepfake Interview Detector",
"version": "1.0",
"permissions": ["activeTab", "scripting"],
"host_permissions": ["https://meet.google.com/*"],
"content_scripts": [
{
"matches": ["https://meet.google.com/*"],
"js": ["content.js"]
}
]
}
content.js
// Runs on Google Meet pages β captures the remote video stream
const API = 'http://localhost:8000/predict';
function findRemoteVideo() {
// Google Meet uses <video> elements; pick the largest (main speaker)
const videos = Array.from(document.querySelectorAll('video'));
return videos.sort((a,b) => (b.videoWidth*b.videoHeight) - (a.videoWidth*a.videoHeight))[0];
}
function createOverlay() {
const el = document.createElement('div');
el.id = 'deepfake-overlay';
el.style.cssText = `
position:fixed; top:12px; right:12px; z-index:99999;
background:rgba(0,0,0,0.75); color:white;
padding:8px 16px; border-radius:8px; font-size:16px;
font-family:monospace; pointer-events:none;
`;
el.textContent = 'π Deepfake Detector Loading...';
document.body.appendChild(el);
return el;
}
async function analyzeFrame(video, overlay) {
if (!video || video.readyState < 2) return;
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth || 640;
canvas.height = video.videoHeight || 480;
canvas.getContext('2d').drawImage(video, 0, 0);
const b64 = canvas.toDataURL('image/jpeg', 0.7).split(',')[1];
try {
const res = await fetch(API, {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({image_b64: b64})
});
const d = await res.json();
const emoji = d.label === 'REAL' ? 'β
' : 'π¨';
const color = d.label === 'REAL' ? '#00ff88' : '#ff4444';
overlay.style.borderLeft = `4px solid ${color}`;
overlay.innerHTML = `${emoji} <b style="color:${color}">${d.label}</b> ${(d.confidence*100).toFixed(0)}%`
+ (d.uncertain ? ' β οΈ' : '')
+ `<br><small>${d.latency_ms}ms</small>`;
} catch (e) {
overlay.textContent = 'β API offline';
}
}
// Wait for Meet to load, then start
setTimeout(() => {
const overlay = createOverlay();
setInterval(() => {
const video = findRemoteVideo();
analyzeFrame(video, overlay);
}, 1500); // analyze every 1.5 seconds
}, 4000);
Load the extension in Chrome
- Go to
chrome://extensions - Enable Developer mode (top right)
- Click "Load unpacked" β select the
deepfake_extension/folder - Open Google Meet β the overlay will appear in the top-right corner
STEP 5 β Deployment Options
| Mode | How | When to Use |
|---|---|---|
| Local | uvicorn main:app --port 8000 |
Development & demos |
| Docker | See Dockerfile below | Portable, shareable |
| Cloud (Render/Railway) | Push to GitHub β connect to Render | Hosting for others |
| ngrok tunnel | ngrok http 8000 β share public URL |
Quick demo without hosting |
Dockerfile (optional)
FROM python:3.11-slim
WORKDIR /app
COPY server/requirements_server.txt .
RUN pip install --no-cache-dir -r requirements_server.txt
COPY server/main.py .
COPY models/best_model.pt ./models/best_model.pt
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
docker build -t deepfake-api .
docker run -p 8000:8000 --gpus all deepfake-api
STEP 6 β Performance Tuning Tips
| Tip | Impact |
|---|---|
| Analyze every 1β2 seconds (not every frame) | Reduces API load, GPU stays cool |
| Send frames at 480p max (not 1080p) | ~4Γ smaller payload, faster inference |
Use torch.compile(model) (PyTorch 2.x) |
10-30% faster inference |
| Run MTCNN on CPU, model on GPU | Frees GPU VRAM for model |
| Add a moving average over last 5 predictions | Smoother, less flickery output |
Moving average example (add to content.js or index.html)
const history = [];
function smoothPrediction(label, conf) {
history.push({ label, conf });
if (history.length > 5) history.shift();
const fakeCount = history.filter(h => h.label === 'FAKE').length;
return fakeCount >= 3 ? 'FAKE' : 'REAL'; // majority vote over last 5
}
Quick Start Checklist
[ ] Training complete β best_model.pt exists in models/
[ ] pip install fastapi uvicorn[standard] facenet-pytorch ...
[ ] Create server/main.py (copy code from Step 2c)
[ ] Run: uvicorn main:app --port 8000
[ ] Check: http://localhost:8000/health
[ ] Open index.html in browser β grant webcam permission
[ ] See REAL / FAKE label update every second
[ ] (Optional) Load Chrome extension for Google Meet detection
C:\Users\SHINJAN\anaconda3\envs\deepfake_gpu\python.exe -m uvicorn main:app --host 0.0.0.0 --port 8000 uvicorn main:app --host 0.0.0.0 --port 8000
Model trained on DFD video dataset + InceptionResnetV1 (VGGFace2) backbone.
RTX 4050 GPU inference: ~15β30ms per frame including face detection.