Try out our LIVE Telegram Bot (link below!! 👇)
spoTTing The Difference
TikTok TechJam 2026 Track 5 Submission
Team: 三T
Inspiration
AI-generated images are everywhere now! On social media, in ads, even in news articles. But how do you tell what's real and what's AI?
We wanted to build a detector that works in the real world and not just on clean, pristine images in a lab, but on the compressed, blurry, noisy images people actually share online. The TikTok TechJam challenge forced us to solve this properly: maintain high accuracy across 15 real-world transforms with a frozen decision threshold.
What it does
Our system detects whether an image is AI-generated or real, even after it's been through real-world degradations like:
- JPEG compression (quality 30-90)
- Gaussian blur (σ 0.5-2.0)
- Resize and upscale (0.25x-0.5x)
- Gaussian noise (σ 0.02-0.10)
- Color jitter (±20%)
- Center crop (80%)
Results on the official benchmark:
- Clean accuracy: 98.60%
- Mean transform accuracy: 98.14%
- Worst-case transform: 97.10% (noise)
- AUROC: 0.996+
How to use it:
# Scan a single image
python scan_image.py path/to/image.jpg
# Or use the Telegram bot
python telegram_bot.py
# Send an image to the bot → get "AI" or "REAL" response
How we built it
Architecture
Input Image → CLIP ViT-L/14 (frozen) → 768-dim features → 8 Logistic Regression Probes → Weighted Average → P(AI)
- Backbone: OpenAI CLIP ViT-L/14 (304M params, frozen)
- Probes: 8 lightweight logistic regression classifiers (769 params each)
- Ensemble: Weighted average of P(AI) across all 8 members
- Threshold: Frozen at 0.5 (no tuning on eval data)
The Math
1. Feature Extraction
Given an input image \(x\), we extract features using frozen CLIP:
$$z = \text{CLIP}(x) \in \mathbb{R}^{768}$$
We then L2-normalize to ensure consistent scale:
$$\hat{z} = \frac{z}{|z|_2}$$
2. Logistic Regression Probe
Each probe learns a linear decision boundary:
$$P(\text{AI} \mid \hat{z}) = \sigma(w^\top \hat{z} + b)$$
where \(\sigma(t) = \frac{1}{1 + e^{-t}}\) is the sigmoid function, \(w \in \mathbb{R}^{768}\) is the weight vector, and \(b \in \mathbb{R}\) is the bias.
3. Class Weighting
To fix calibration (push AI probabilities higher), we use weighted loss:
$$\mathcal{L} = -\sum_{i=1}^{N} \left[ w_{\text{AI}} \cdot y_i \log(\hat{y}_i) + w_{\text{real}} \cdot (1-y_i) \log(1-\hat{y}_i) \right]$$
where \(w_{\text{AI}} = 4.5\) and \(w_{\text{real}} = 1.0\) for most probes.
4. Ensemble Averaging
The final prediction is the weighted average across all 8 probes:
$$P_{\text{final}}(\text{AI} \mid \hat{z}) = \frac{1}{8} \sum_{k=1}^{8} P_k(\text{AI} \mid \hat{z})$$
5. Transform Augmentation
For each AI training image \(x_i\), we generate augmented copies:
$$\mathcal{D}_{\text{aug}} = {x_i, T_1(x_i), T_2(x_i), T_3(x_i)}$$
where \(T_j\) are random transforms (JPEG, blur, noise) sampled from the competition's 15-transform spec.
Key Innovation 1: Transform Augmentation
Problem: Our baseline dropped to 73-75% accuracy under noise and blur.
Solution: For each AI training image, we created 3 random transformed copies (JPEG, blur, noise) during feature extraction. This exposed the model to degraded AI images during training.
Result: Mean transform accuracy improved from 89.2% → 93.29%; worst-case from 78.1% → 91.9%.
Key Innovation 2: Pure Single-Generator Probes
Problem: Our 5-member ensemble (all trained on mixed generators) plateaued at 97.39%. Adding more "superset" probes made it worse.
Insight: Superset probes are highly correlated: they all learn similar patterns. We needed diversity.
Solution: Train individual probes on ONE generator's AI images + real negatives only. Each pure probe learns generator-specific signatures.
Result: Adding 3 pure probes (SD3-only, Midjourney-only, Sana-only) improved mean accuracy from 97.39% → 98.14%; worst-case from 96.80% → 97.10%.
Training Data
We combined 6 independent AI image sources:
- SID_Set (Hugging Face): 8,000 images (4,000 real + 4,000 AI)
- ComfyUI (own generator): 8,000 AI images
- Flux_AIGC_Dataset: 3,000 AI images
- Sana: 2,000 AI images
- Midjourney: 3,000 AI images
- SD3-medium: 6,600 AI images
Total: ~130,000 features (with augmentation)
Challenges we ran into
1. CIFAKE Dilution
Issue: Including CIFAKE (32×32 images) in training hurt performance on natural-resolution images.
Finding: CIFAKE's low resolution created conflicting signals. Dropping it improved clean accuracy from 81.1% → 88.0%.
2. Calibration vs. Discrimination
Issue: Training on SID_Set alone gave excellent AUROC (0.983) but poor accuracy (0.891) at the frozen t=0.5 threshold.
Diagnosis: The model ranked images correctly (high AUROC) but pushed AI probabilities too low. This was a calibration problem, not a discrimination problem.
Solution: Class weighting — upweighting the AI class during training shifted the decision boundary. At ai_weight=5.0, clean accuracy jumped to 92.0%.
3. Superset Probes Hurt Ensembles
Issue: Adding a 6th probe trained on (all generators + SD3) made the ensemble worse (97.18% vs 97.39%).
Root Cause: The superset probe was highly correlated with existing members — it didn't add diversity, just redundant information.
Lesson: Ensembles need diverse members, not just more members.
4. Feature Drift Doesn't Transfer
Issue: We hypothesized that AI images show larger embedding drift under perturbations. We added drift features [drift_mean, drift_std] to CLIP features.
Result: The drift probe (0.8920 clean, 0.6760 worst) was worse than our baseline and got worse under noise (AI accuracy dropped from 0.79 → 0.35 as noise increased).
Lesson: Drift is a real signal, but CLIP's drift isn't stable enough to use as a feature.
5. Noise Augmentation Skews Class Balance
Issue: We tried retraining all 8 probes with noise-augmented features (each image + 4 noise levels).
Result: The "Noisy8" ensemble was worse across all transforms (96.90% clean vs 98.60% baseline).
Root Cause: Adding 68k AI noise features skewed the class balance heavily toward AI, shifting the decision boundary. CLIP features don't capture added noise in a generalizable way.
Lesson: More data isn't always better — you need balanced, diverse data.
6. Ensemble Saturation
Issue: Adding a 9th probe (pure-Flux) made the ensemble worse (98.04% vs 98.60%).
Root Cause: One more AI-focused probe tipped the balance toward over-flagging noise as AI.
Lesson: There's an optimal ensemble size. For our setup, 8 members is optimal.
7. scikit-learn Version Compatibility
Issue: Our probes were trained with scikit-learn 1.9.0 (requires Python 3.11+), but the original venv was Python 3.10.
Error: 'LogisticRegression' object has no attribute 'multi_class'
Solution: Created a new venv with Python 3.14 + scikit-learn 1.9.0.
Accomplishments that we're proud of
- 98.14% mean transform accuracy : maintaining high accuracy across 15 real-world degradations
- Discovered the pure single-generator probe technique : a novel ensemble diversity strategy
- Solved the calibration problem : using class weighting to fix probability shifts without retraining the backbone
- Built a working Telegram bot : real-time AI image detection via messaging app
- Stayed well under the 2B parameter limit : only 304M params (15.2% of budget)
What we learned
- Ensembles need diversity, not just quantity : correlated members hurt performance
- Augmentation is critical for robustness : training on transformed images dramatically improves real-world performance
- Calibration ≠ Discrimination : a model can rank correctly (high AUROC) but still fail at a fixed threshold
- More data isn't always better : imbalanced or redundant data can hurt more than help
- Version compatibility matters : always pin your dependencies and document your environment
What's next for spoTTing The difference
Short-term improvements
- Higher-resolution training: Extract features at native resolution for better performance on high-res images
- Frequency-domain features: Add FFT-based features to capture subtle AI artifacts
- More generators: Add pure probes for Flux, Ideogram, Krea, and other emerging generators
Long-term vision
- Video detection: Extend to video by analyzing frame consistency and temporal patterns
- Online learning: Adapt to new generators without full retraining
- Explainability: Add attention maps or feature importance to show why an image is flagged
- Mobile deployment: Quantize the model for on-device inference
Known limitations
- Resolution: Optimized for lower-resolution images due to time and storage constraints
- Noise robustness: Noise transforms (σ0.05/0.10) remain our weakest point at 97.10%
- Generator coverage: Newer generators may not be as well-detected
- Single-frame only: Doesn't leverage temporal consistency for video
Repository
Quick Start:
pip install -r requirements.txt
python scan_image.py path/to/image.jpg
How to use Telegram bot
Click on the telegram link below 👇 /Start and just send in your image to detect if it's AI!
Log in or sign up for Devpost to join the conversation.