AI_OR_NOT_CLASSIFICATION / MODEL_ARCHITECTURE.md
ammarsapru's picture
Publish AI vs Real image detector
29271db verified
|
Raw
History Blame Contribute Delete
4.76 kB
# Model Architecture
**AI vs Real Image Detector β€” 3-Branch Ensemble**
Input: RGB image resized to **224Γ—224**, pixels in `[0, 1]`.
Output: 2 logits β†’ softmax. **Label map: `0 = real`, `1 = ai`.**
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
224Γ—224 RGB ───────►│ Branch 1: CLIP (frozen) │──► 768
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚ Branch 2: EfficientNet-B3 │──► 1536
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
└──────────────►│ Branch 3: FFT-CNN │──► 512
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ concat (2816)
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Fusion MLP β”‚
β”‚ 2816 β†’ 512 β†’ 128 β†’ 2 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β–Ό
[P(real), P(ai)]
```
---
## Branch 1 β€” CLIP (semantic view)
- **Backbone:** OpenAI **CLIP ViT-L-14** visual encoder, **fully frozen**
(`requires_grad = False`), run in `eval()` under `torch.no_grad()`.
- **Normalization:** CLIP's own mean/std, applied as registered buffers.
- **Adapter (trainable):** `Linear(768β†’768) β†’ LayerNorm β†’ GELU β†’ Dropout(0.1) β†’
Linear(768β†’768) β†’ LayerNorm`.
- **Output:** 768-d.
- **Rationale:** CLIP encodes high-level semantic structure; the adapter
re-projects those features toward the real-vs-AI decision without disturbing
the pretrained encoder (parameter-efficient, overfitting-resistant).
## Branch 2 β€” EfficientNet-B3 (texture view)
- **Backbone:** torchvision **EfficientNet-B3**, ImageNet-pretrained,
**fine-tuned** (features + avgpool).
- **Normalization:** ImageNet mean/std (registered buffers).
- **Head:** Dropout(0.4) after global average pooling.
- **Output:** 1536-d.
- **Rationale:** captures local texture and spatial artifacts β€” skin, edges,
fine detail β€” where generators leave subtle inconsistencies.
## Branch 3 β€” FFT-CNN (frequency view)
- **Spectral transform:** convert to grayscale (luma), 2D FFT, `log1p(|Β·|)`
magnitude, `fftshift`, then per-image min-max normalize to `[0, 1]`.
- **CNN:** 4 conv blocks `1β†’32β†’64β†’128β†’256` (3Γ—3, BN, ReLU, MaxPool between the
first three), `AdaptiveAvgPool2d(1)`, flatten.
- **Projection:** `Linear(256β†’512) β†’ LayerNorm β†’ GELU`.
- **Output:** 512-d.
- **Rationale:** GAN/diffusion upsampling leaves periodic grid patterns in the
frequency domain that are invisible in pixel space. This branch reads them
directly. (Note: this is also why *resizing* an image β€” which injects
interpolation artifacts β€” can fool the branch; see limitations.)
## Fusion head
- Concatenate `[768 + 1536 + 512] = 2816`.
- `Linear(2816β†’512) β†’ BN β†’ GELU β†’ Dropout(0.4) β†’ Linear(512β†’128) β†’ BN β†’ GELU β†’
Dropout(0.2) β†’ Linear(128β†’2)`.
## Parameters
| | Count |
|---|---|
| Total | ~317,877,130 |
| Trainable | ~13,910,922 |
Trainable = CLIP adapter + EfficientNet + FFT branch + fusion head. The CLIP
backbone (the bulk of the params) is frozen.
## Training setup
- **Optimizer:** AdamW with **per-branch learning rates** β€” CLIP adapter `1e-5`,
EfficientNet `3e-5`, FFT `1e-4`, fusion head `1e-4`; weight decay `1e-4`.
- **Scheduler:** CosineAnnealingLR (`eta_min = 1e-7`).
- **Loss:** class-weighted CrossEntropy. **Grad clipping:** `1.0`.
- **Sampling:** WeightedRandomSampler for class balance.
- **Augmentation:** horizontal flip, color jitter, random grayscale, Gaussian
blur.
## Checkpoint format
`best_model.pt` is a dict:
```python
{"epoch": int, "model_state": state_dict, "val_f1": float, "cfg": {...}}
```
Load via `model.py`'s `load_detector(path, device)`, which rebuilds the network
from the embedded `cfg` and loads `model_state`. `AutoModel`/`pipeline()` do
**not** work β€” this is a custom `nn.Module`.