Upload an image and find out whether it's AI-generated. Instead of a bare yes/no, you get a confidence score and a risk classification (likely real / likely fake), backed by a ResNet18 fine-tuned to 97.9% test accuracy.
- DeepShield β AI Deepfake Image Detection
AI-generated imagery is getting hard to spot by eye. I wanted a tool that returns a defensible confidence score and risk classification β not just a binary answer β so you can actually reason about how trustworthy an image is instead of trusting a single flashing label.
So I fine-tuned an image classifier to tell real photos apart from AI-generated faces, wrapped it in a small inference API, and built a front-end where you can drop in an image and get an explained result.
| Landing page | Upload flow | Result card |
|---|---|---|
![]() |
![]() |
![]() |
- π§ Fine-tuned ResNet18 β transfer learning from ImageNet, retargeted to a real-vs-fake classifier.
- π Confidence + risk classification β a softmax confidence score bucketed into Low / Medium / High risk, not just a label.
- β‘ Fast FastAPI inference β upload to result in well under a second on CPU.
- π¨ Polished React front-end β drag-and-drop upload with an animated, explained result breakdown.
- π Reproducible training & evaluation β the full PyTorch pipeline (
train.py,evaluate.py) lives in the repo, and every metric below was actually measured.
Measured by evaluate.py on 4,000 held-out test images the model never saw during training:
| Metric | Score |
|---|---|
| Accuracy | 97.9% |
| Precision | 98.5% |
| Recall | 97.2% |
| F1 score | 97.8% |
Confusion matrix (FAKE treated as the positive class, 4,000 images):
| Predicted FAKE | Predicted REAL | |
|---|---|---|
| Actual FAKE | 1,926 β | 55 β (missed) |
| Actual REAL | 30 β (false alarm) | 1,989 β |
Reading it plainly: of the fakes, it caught 1,926 and missed 55; of the real images, it correctly passed 1,989 and only false-alarmed on 30. High precision (98.5%) means when it says "fake," it's almost always right; high recall (97.2%) means it rarely lets a fake slip through.
ββββββββββββββββ image upload βββββββββββββββββββββ tensor ββββββββββββββββ
User ββ Next.js UI β ββββββββββββββββΆ β FastAPI /analyze β ββββββββββΆ β ResNet18 β
β (upload zone)β β (main.py) β β (model.py) β
ββββββββββββββββ ββββββββββββββββ βββββββββββββββββββββ ββββββββββ ββββββββββββββββ
verdict + confidence + risk + breakdown (JSON)
- The user uploads an image in the React front-end.
- The frontend POSTs it as
multipart/form-datato the FastAPI/analyzeendpoint. - The image is resized to 224Γ224, normalised with ImageNet mean/std, and run through the fine-tuned ResNet18.
- A softmax turns the two output logits into probabilities; the top one becomes the confidence score, which is bucketed into a risk level.
- The API returns a JSON verdict + breakdown, which the UI animates into a result card.
The model is a ResNet18 pretrained on ImageNet, fine-tuned to separate real photos from AI-generated faces. Full pipeline in train.py.
Dataset: 140k Real and Fake Faces β real photos vs StyleGAN-generated faces. It ships with its own train/ valid/ test/ split (each holding real/ and fake/ folders), so I use those official splits directly rather than doing my own random split. Classes are read alphabetically, so fake β 0, real β 1.
Approach β transfer learning: I start from ImageNet weights and swap the final fully-connected layer for a 2-class output (model.fc = Linear(in_features, 2)), then fine-tune. I didn't train from scratch β with a task like this, ImageNet's learned low-level features (edges, textures, colour patterns) transfer well and get you good accuracy without needing huge data or compute.
Augmentation (training images only β val/test stay untouched so scores stay honest):
RandomHorizontalFlipRandomRotation(10)ColorJitter(brightness=0.2, contrast=0.2)
Hyperparameters:
| Setting | Value |
|---|---|
| Backbone | ResNet18 (ImageNet pretrained) |
| Input size | 224 Γ 224 |
| Optimizer | Adam |
| Learning rate | 1e-4 |
| Loss | Cross-entropy |
| Batch size | 32 |
| Epochs | 5 |
| Train images (capped for speed) | 20,000 |
| Eval images (capped for speed) | 4,000 |
Training is meant to run on a GPU (I used Google Colab). train.py optionally downloads the dataset via kagglehub, fine-tunes, and saves models/deepfake_model.pth. evaluate.py then loads those weights, runs them on the untouched test split, builds the confusion matrix by hand, and writes metrics.json β the exact numbers in the Results table.
Base URL (local): http://127.0.0.1:8000
Analyze a single image.
Request β multipart/form-data:
| Field | Type | Description |
|---|---|---|
file |
file | The image to analyze (required) |
mediaType |
string | Always "image" (kept for the frontend) |
curl -X POST http://127.0.0.1:8000/analyze \
-F "file=@some_image.jpg" \
-F "mediaType=image"Response β 200 OK:
{
"verdict": "ORIGINAL",
"confidence": 98.4,
"riskLevel": "Low",
"breakdown": [
{ "label": "Face Manipulation", "detected": false, "detail": "No facial manipulation detected" },
{ "label": "GAN Artifacts", "detected": false, "detail": "No GAN artifacts found" },
{ "label": "Inconsistent Lighting", "detected": false, "detail": "Lighting appears consistent" },
{ "label": "Compression Analysis", "detected": false, "detail": "Compression patterns appear normal" }
],
"analysisTime": "0.12s"
}Honest note on
breakdown: these are not four separate detectors. The model produces a single real-vs-fake confidence score; the breakdown is a human-readable expansion of that one score against a few thresholds, so the UI can show something interpretable rather than a lone number. I'd rather be upfront about that than pretend it's a multi-signal forensic pipeline.
Error responses: 400 for an empty or non-image file, 503 if the model weights failed to load on the server.
Returns the measured test-set metrics (metrics.json) β accuracy, precision, recall, F1, and the confusion matrix.
Simple health check: { "status": "ok", "modelLoaded": true }.
deepfake-detection/
ββ deepfakedetectionbackend/ # FastAPI + PyTorch
β ββ main.py # /analyze, /metrics, /health endpoints
β ββ model.py # DeepfakeDetector: loads weights, predicts
β ββ train.py # fine-tuning pipeline (run on Colab/GPU)
β ββ evaluate.py # computes metrics on the test split
β ββ metrics.json # measured test-set metrics
β ββ models/deepfake_model.pth # trained weights (~44 MB)
β ββ requirements.txt
ββ DeepfakeDetectionFrontend/ # Next.js front-end
ββ app/ # routes: / , /detect/image , /about
ββ components/ # landing, detection and UI components
ββ lib/api.ts # talks to the backend
- Python 3.9+
- Node.js 18+
- The trained weights at
deepfakedetectionbackend/models/deepfake_model.pth(included)
cd deepfakedetectionbackend
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
uvicorn main:app --reload # runs on http://127.0.0.1:8000Quick check: open http://127.0.0.1:8000/health β it should report "modelLoaded": true.
Tip: activate the virtualenv (
source .venv/bin/activate) and runuvicornfrom insidedeepfakedetectionbackend/, so themodelimport and the weights path resolve correctly.
cd DeepfakeDetectionFrontend
npm install
cp .env.example .env.local # optional: defaults to the local backend
npm run dev # runs on http://localhost:3000Open http://localhost:3000 and upload an image.
| Variable | Where | Default | Purpose |
|---|---|---|---|
NEXT_PUBLIC_API_BASE_URL |
frontend | http://127.0.0.1:8000 |
Base URL of the FastAPI backend |
Training is meant to run on a GPU (e.g. Google Colab):
python train.py # downloads data, fine-tunes, saves models/deepfake_model.pth
python evaluate.py # runs on the test split, prints + saves metrics.jsonA few choices I made and why β the reasoning matters more than the code here:
- Fine-tune, don't train from scratch. Training a CNN from scratch needs a lot of data and compute and overfits easily on a smaller set. Transfer learning from ImageNet reuses strong general visual features and gets good accuracy cheaply β the right call for a single-GPU/Colab project.
- ResNet18 over bigger backbones. It's the smallest ResNet: fast to train, fast at inference (keeps uploadβresult latency low), and small enough to ship. ResNet50/EfficientNet would want more data and compute to justify.
- Confidence + risk, not a binary label. A tool you can trust should tell you how sure it is. I take the softmax probability as confidence and bucket it into Low/Medium/High risk so the output is interpretable.
- Score the model honestly. Augmentation is applied to training images only; validation and test images are left untouched, so the reported numbers reflect real performance rather than being flattered by augmentation.
- Fail gracefully, don't 500. The API returns clear
400/503errors for empty files, non-images, or a missing model, instead of crashing. - Keep the response honest. The
breakdownis presented as an expansion of one score, not faked as multiple independent detectors (see the note under/analyze).
- The model is trained on a face dataset (real photos vs StyleGAN faces), so it's strongest on AI-generated faces. Images from other generators (Midjourney, DALLΒ·E, Stable Diffusion) or non-face images fall outside its training distribution and can be misclassified β sometimes confidently. It's a strong signal, not a guarantee.
- Heavy compression or resizing can wash out the generator "fingerprint" the model relies on and lower accuracy.
- What's next:
- A Grad-CAM heatmap to show where the model thinks an image is fake.
- A larger, more diverse multi-generator dataset to improve generalization.
- Trying a stronger backbone (e.g. EfficientNet) or an ensemble.
Built by Lakshay Tuteja β GitHub @lucy-04 Β· lakshay.tuteja004@gmail.com


