Downloads · 30 days
0
Medsa/ai-image-authenticity-detector
ai-image-authenticity-detector is a image classification model from Medsa. Use it when you need a label for an image. It is set up for pytorch.
Classifies images as AI-generated (FAKE) or authentic (REAL) using a multi-branch convolutional neural network that analyses spatial, frequency, and noise-level features simultaneously.
Downloads · 30 days
0
Access
Public
Updated Mar 7, 2026
Repo size
30.3 MB
Likes
1
Public
Click a slice to open those files.
.pt30.3 MB · 100%
From the Hugging Face model README
Classifies images as AI-generated (FAKE) or authentic (REAL) using a multi-branch convolutional neural network that analyses spatial, frequency, and noise-level features simultaneously.
The model is exported as a self-contained TorchScript file — no architecture definition is required to run it.
| Property | Value |
|---|---|
| Architecture | ImprovedDetector (multi-branch CNN) |
| Task | Binary image classification (FAKE / REAL) |
| Input | RGB image, resized to 32 × 32, normalized to [-1, 1] |
| Output | (logit, gates) — scalar logit + 3-weight gate vector |
| Decision threshold | sigmoid(logit) > 0.5 → FAKE |
| Format | TorchScript (.pt) |
The model is built around three independent branches that each look for a different class of evidence, feeding into a shared gated fusion layer.
Processes raw pixel data through a stem convolution followed by three residual blocks. Looks for visual artifacts, unnatural textures, and lighting inconsistencies that generative models leave behind.
Operates in the frequency domain by computing the FFT magnitude, FFT phase, and a DCT approximation of the input — each processed by its own convolutional block. Detects the spectral fingerprints that generative models produce but real camera sensors don't.
Applies a fixed high-pass filter to isolate the high-frequency noise layer of the image, then analyses it with a small CNN. Real photographs carry consistent sensor noise; AI images tend to have suspiciously smooth or periodically structured noise patterns.
All three branch embeddings are concatenated and passed through a two-layer gate network with a Softmax output, producing three scalar weights that sum to 1.0. These weights are used to compute a weighted sum of projected branch features before the final classifier. The gate weights are returned alongside the logit, giving per-image explainability.
Input image (3 × 32 × 32)
│
┌────┴────────────────────┐
│ │ │
Spatial Frequency Noise
(512-d) (512-d) (256-d)
│ │ │
└────────────┴────────────┘
│
Gated Fusion
(learned weights → 512-d)
│
Classifier
(512 → 256 → 64 → 1)
│
logit
import torch
from PIL import Image
import torchvision.transforms as T
from huggingface_hub import hf_hub_download
# Load model
path = hf_hub_download("Medsa/ai-image-authenticity-detector", "detector_scripted.pt")
model = torch.jit.load(path, map_location="cpu")
model.eval()
# Preprocess
transform = T.Compose([
T.Resize((32, 32)),
T.ToTensor(),
T.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]),
])
img = Image.open("photo.jpg").convert("RGB")
tensor = transform(img).unsqueeze(0) # (1, 3, 32, 32)
# Inference
with torch.no_grad():
logit, gates = model(tensor)
fake_prob = torch.sigmoid(logit).item()
verdict = "FAKE" if fake_prob > 0.5 else "REAL"
print(f"Verdict : {verdict}")
print(f"Fake prob : {fake_prob:.4f}")
print(f"Real prob : {1 - fake_prob:.4f}")
print(f"Gates : spatial={gates[0,0]:.3f} freq={gates[0,1]:.3f} noise={gates[0,2]:.3f}")
The model is also served by the TruthLens web application:
curl -X POST http://localhost:5000/predict \
-F "[email protected]"
The model returns a tuple (logit, gates):
| Output | Shape | Description |
|---|---|---|
logit | (1,) | Raw pre-sigmoid score. Pass through sigmoid() to get fake_prob. |
gates | (1, 3) | Softmax-normalized branch weights: [spatial, frequency, noise] |
fake_prob = torch.sigmoid(logit).item() # probability image is AI-generated
real_prob = 1 - fake_prob # probability image is authentic
spatial_weight = gates[0, 0].item()
frequency_weight = gates[0, 1].item()
noise_weight = gates[0, 2].item()
Gate weights indicate which branch was most influential for a given image. They always sum to 1.0.
The model expects images preprocessed exactly as follows:
transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor(), # scales to [0, 1]
transforms.Normalize([0.5, 0.5, 0.5], # shifts to [-1, 1]
[0.5, 0.5, 0.5]),
])
Inputs that are not normalized to [-1, 1] will produce unreliable results.
| File | Description |
|---|---|
detector_scripted.pt | TorchScript model — load with torch.jit.load() |
Dataset: CIFAKE — a balanced dataset of real CIFAR-10 images and AI-generated counterparts at 32 × 32 resolution.
Optimizer: AdamW (lr=3e-4, weight_decay=1e-4) with cosine annealing down to 1e-6 over 50 epochs, batch size 64, gradient clipping at norm 1.0. Early stopping with patience 5.
Data augmentation (train only): random horizontal/vertical flips, color jitter (brightness, contrast, saturation), and small random rotation (±10°).
Loss function: standard BCE plus a gate diversity penalty to prevent branch collapse — an issue where the gate learns to ignore one or more branches entirely and routes everything through a single one:
L = BCE(logit, label) − λ · H(gates)
where H(gates) is the mean gate entropy across the batch. Maximising entropy keeps all three branches actively contributing. The weight λ starts at 0.3 and decays by 0.9 per epoch (floored at 0.05), so the penalty is strong early on to force branch diversity, then relaxes as the gate is allowed to specialise.