Inspiration

Generative AI has made photorealistic fakes cheap and scalable, and that directly threatens platform trust. But the detectors that score well in papers tend to fall apart the moment an image is JPEG-re-encoded, cropped to a profile picture, run through a filter app, and reposted. The hard problem isn't clean-image accuracy; it's staying accurate after an image has been through the internet.

A steer from the TikTok team shaped our whole approach: go hybrid by combining high-level CLIP semantics with low-level frequency patches, and not just fine-tuning a classifier.

What it does

Given an image (or a directory), the model outputs a confidence score that the image is AI-generated, as a JSON file of {image_path, pred} — the required deliverable format.

The final model, hybrid_effb1, has two branches:

  • Semantic branch — frozen CLIP ViT-B/32. A "does this scene look plausible" prior. It never trains, so it can't memorise a specific generator's fingerprint, and its features barely move under compression, blur, cropping or colour shifts — this branch is what keeps accuracy up when an image is degraded.
  • Low-level branch — two fused sub-streams. A fixed high-pass residual (image − blur(image)) fed to an EfficientNet-B1 feature extractor, plus a per-patch 2D-FFT log-magnitude spectrogram fed to a small CNN. Together they catch the upsampling / checkerboard / spectral artifacts of generation.
  • A small fusion head learns to lean on the low-level branch when an image is clean and fall back on CLIP semantics when it isn't.

Around the model we built a 15-transform robustness suite (the 6 families in the brief), a cross-source generalisation test, a web dashboard for interactive analysis, and a full error-analysis note.

How we built it

Data & anti-leakage. We trained on CIFAKE (Kaggle) plus a filtered subset of WildFake (ModelScope). Because the demonstration benchmark (COCO val2017 + DALL·E Advanced) is carved out of WildFake, we built holdout_index.py — it indexes every benchmark image by SHA-256 and perceptual hash — and prepare_wildfake.py drops any training candidate that matches on either, catching re-encoded and resized copies that a filename check would miss.

Robustness-aware training. On-the-fly augmentation samples the same 6 corruption families used in scoring, at most one per image, never stacked, at random severities deliberately kept off the exact evaluation grid points — so the robustness score measures generalisation, not memorisation of the harness.

Model selection. We benchmarked plain EfficientNet-B0/B1/B2: B1 was the sweet spot (B0 underfits robustness, B2 adds parameters for no gain and worse calibration). We then built the hybrid, and finally swapped its small spatial CNN for EfficientNet-B1 → hybrid_effb1, which won on clean accuracy, on all six robustness families, on all 15 transforms — and whose advantage over the lighter variant grew out-of-distribution (+0.008 AUC cross-source vs. +0.003 clean), the strongest evidence we had that the extra capacity generalises rather than memorises.

Compute. Freezing CLIP means only ~7M parameters train, so the whole thing fits a 4 GB consumer GPU (GTX 1650 SUPER) at batch 16.

Built with: VS Code, Git/GitHub, a local NVIDIA GPU + Apple-Silicon MPS · Models/APIs: CLIP openai/clip-vit-base-patch32 (Hugging Face Transformers), TorchVision EfficientNet-B0/B1/B2 · Libraries: PyTorch, TorchVision, Transformers, scikit-learn, NumPy, Pillow, pandas, tqdm, Matplotlib, Flask · Datasets: CIFAKE, WildFake (filtered subset), SID_Set (evaluated).

Challenges we ran into

  • Calibration collapse under domain shift. The ROC-optimal decision threshold drops from ~0.65 on CIFAKE to ~0.003 on the cross-source set — the model's ranking generalises, its absolute scores don't. We report raw probabilities in the deliverable and load a fitted threshold in the demo rather than assuming 0.5.
  • Compute limits. We started on a CPU-only PyTorch wheel; the full WildFake dataset is ~1.6 TB and we could only pull ~18 GB; training was capped at 8 epochs on a subsampled set.
  • The frequency branch is fragile exactly where it's needed — blur, resize and JPEG destroy the high-frequency signal it depends on, so under the worst corruptions robustness rests almost entirely on CLIP.

Accomplishments we're proud of

  • A detector that degrades gracefully: 1.2 AUC points lost across all 15 real-world corruptions, nothing below 0.95.
  • Evidence of genuine generalisation, not shortcut memorisation: the hybrid's edge over the lighter model is larger on unseen generators than on clean data.
  • 40% fewer false positives than the baseline hybrid where fewer real photos wrongly accused, the costlier error for a platform.
  • An honest error analysis: our representative false positives are real photos that look "too clean" (flat skies, low noise, saturated colour); our false negatives are AI images that fake photographic imperfection (simulated film grain, aged-manuscript texture, photoreal diorama lighting).

What we learned

  • Clean-data accuracy is the least informative number in this problem. Robustness and cross-source generalisation are what matter, and they can diverge sharply from it.
  • Bigger isn't automatically better (B2 lost to B1), but adding capacity exactly where the signal lives — the low-level spatial branch — pays off.
  • Ranking and calibration are separate problems. ROC-AUC can stay stable while a fixed decision threshold silently fails across domains.
  • Freezing a strong pretrained encoder is a cheap, effective shortcut-resistance mechanism.

None of our five team members had any machine-learning background coming in and this problem statement was our first hands-on encounter with training, evaluating and debugging a model. We came out understanding dataset shortcuts, robustness testing, calibration and generalisation far better than any tutorial would have taught us.

What's next

  • Per-generator AUC and leave-one-generator-out experiments; run the official COCO val2017 + DALL·E Advanced benchmark.
  • Broaden training generators (GANs, Midjourney, SD variants, autoregressive models) and integrate SID_Set.
  • Temperature scaling / per-domain threshold calibration for reliable hard labels.
  • Region-level localisation of AI edits using SID_Set masks.
  • Grad-CAM / integrated-gradients attribution on the false-positive and false-negative sets to characterise failure modes.

Done By: Claire, Clarice, Tesmond, Sabrina, Xin Yin

Built With

+ 91 more
Share this project:

Updates