Costi Claude Sonnet 5 commited on
Commit ·
d9a3537
1
Parent(s): 1d88bd9
Add real eval results and reproduction guide
Browse filesRuns an actual COCO-captions FID/CLIP Score eval against the model's
real generated outputs rather than filling the leaderboard template
with placeholder numbers, and documents the model's actual capability
(toy MLP, not general text-to-image) alongside the measurements.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- EVAL_REPRODUCTION.md +155 -0
- eval.md +61 -0
EVAL_REPRODUCTION.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Reproducing the eval.md numbers
|
| 2 |
+
|
| 3 |
+
Step-by-step guide to reproduce the FID / CLIP Score numbers in `eval.md` from scratch. Everything below was run in a throwaway virtualenv outside this repo; none of it is required to just *use* PixelModel (see `README.md` for that) — it's only needed if you want to re-run or extend the evaluation.
|
| 4 |
+
|
| 5 |
+
Uses Python 3.13 (CPU-only; no GPU required, but slower). Total download size is roughly 1.5–2GB (torch, torchvision, CLIP weights, Inception weights) — pick an install location with that much free space.
|
| 6 |
+
|
| 7 |
+
## 1. Create an isolated venv
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
python3.13 -m venv eval-venv
|
| 11 |
+
# Windows:
|
| 12 |
+
eval-venv\Scripts\activate
|
| 13 |
+
# macOS/Linux:
|
| 14 |
+
source eval-venv/bin/activate
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
## 2. Install dependencies
|
| 18 |
+
|
| 19 |
+
CPU-only torch keeps the install small (~130MB vs several GB for a CUDA build):
|
| 20 |
+
|
| 21 |
+
```bash
|
| 22 |
+
pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
|
| 23 |
+
pip install transformers scipy huggingface_hub "datasets<3" pyarrow pytorch-fid
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
`datasets<3` is pinned because `pytorch-fid==0.3.0`'s dependency chain and the CLIP scoring code below weren't tested against `datasets` 3.x.
|
| 27 |
+
|
| 28 |
+
## 3. Fetch a real COCO caption/image sample
|
| 29 |
+
|
| 30 |
+
This streams only the first N rows from a public 30K-pair COCO val2014 dataset — it does **not** download the full ~5GB dataset.
|
| 31 |
+
|
| 32 |
+
```python
|
| 33 |
+
# fetch_coco_sample.py
|
| 34 |
+
import os, json, time
|
| 35 |
+
from datasets import load_dataset
|
| 36 |
+
|
| 37 |
+
N = 40
|
| 38 |
+
OUT_DIR = "coco_sample"
|
| 39 |
+
os.makedirs(OUT_DIR, exist_ok=True)
|
| 40 |
+
|
| 41 |
+
ds = load_dataset("sayakpaul/coco-30-val-2014", split="train", streaming=True)
|
| 42 |
+
|
| 43 |
+
meta = []
|
| 44 |
+
for i, row in enumerate(ds):
|
| 45 |
+
if i >= N:
|
| 46 |
+
break
|
| 47 |
+
img = row["image"]
|
| 48 |
+
caption = row["caption"]
|
| 49 |
+
fname = f"real_{i:03d}.jpg"
|
| 50 |
+
img.convert("RGB").save(os.path.join(OUT_DIR, fname), format="JPEG", quality=90)
|
| 51 |
+
meta.append({"file": fname, "caption": caption})
|
| 52 |
+
|
| 53 |
+
with open(os.path.join(OUT_DIR, "captions.json"), "w") as f:
|
| 54 |
+
json.dump(meta, f, indent=2)
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
Run it: `python fetch_coco_sample.py` — takes ~10 seconds, ~3.5MB on disk. Raise `N` for a less noisy (but slower, and eventually much larger) eval — real COCO FID evals typically use N=30,000.
|
| 58 |
+
|
| 59 |
+
## 4. Generate PixelModel outputs for those captions
|
| 60 |
+
|
| 61 |
+
Run from inside this repo (needs `model.py` and `model.png` on the path):
|
| 62 |
+
|
| 63 |
+
```python
|
| 64 |
+
# generate_outputs.py
|
| 65 |
+
import os, json
|
| 66 |
+
import numpy as np
|
| 67 |
+
from PIL import Image
|
| 68 |
+
import torch
|
| 69 |
+
from model import load_model, forward # from this repo
|
| 70 |
+
|
| 71 |
+
META_PATH = "coco_sample/captions.json"
|
| 72 |
+
OUT_DIR = "gen_sample"
|
| 73 |
+
os.makedirs(OUT_DIR, exist_ok=True)
|
| 74 |
+
|
| 75 |
+
with open(META_PATH) as f:
|
| 76 |
+
meta = json.load(f)
|
| 77 |
+
|
| 78 |
+
pixels = load_model("model.png")
|
| 79 |
+
|
| 80 |
+
for i, row in enumerate(meta):
|
| 81 |
+
with torch.no_grad():
|
| 82 |
+
result = forward(pixels, row["caption"])
|
| 83 |
+
arr = (result.numpy() * 255).clip(0, 255).astype(np.uint8)
|
| 84 |
+
Image.fromarray(arr, mode="RGB").save(os.path.join(OUT_DIR, f"gen_{i:03d}.png"))
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
## 5. Compute FID
|
| 88 |
+
|
| 89 |
+
`pytorch-fid==0.3.0` calls `scipy.linalg.sqrtm(..., disp=False)`, which recent scipy versions (1.14+) no longer accept — this reimplements the Fréchet distance calculation without that removed kwarg instead of pinning an old scipy:
|
| 90 |
+
|
| 91 |
+
```python
|
| 92 |
+
# compute_fid.py
|
| 93 |
+
import numpy as np
|
| 94 |
+
from scipy import linalg
|
| 95 |
+
from pytorch_fid.fid_score import compute_statistics_of_path
|
| 96 |
+
from pytorch_fid.inception import InceptionV3
|
| 97 |
+
|
| 98 |
+
def calculate_frechet_distance(mu1, sigma1, mu2, sigma2):
|
| 99 |
+
diff = mu1 - mu2
|
| 100 |
+
covmean = linalg.sqrtm(sigma1.dot(sigma2))
|
| 101 |
+
if np.iscomplexobj(covmean):
|
| 102 |
+
covmean = covmean.real
|
| 103 |
+
return diff.dot(diff) + np.trace(sigma1) + np.trace(sigma2) - 2 * np.trace(covmean)
|
| 104 |
+
|
| 105 |
+
dims = 2048
|
| 106 |
+
model = InceptionV3([InceptionV3.BLOCK_INDEX_BY_DIM[dims]]).to("cpu")
|
| 107 |
+
|
| 108 |
+
# NOTE: real images are JPEGs of varying resolution; pytorch-fid's default
|
| 109 |
+
# dataloader batches images without resizing first, which crashes on a batch
|
| 110 |
+
# of mismatched sizes — use batch_size=1 to sidestep that.
|
| 111 |
+
m1, s1 = compute_statistics_of_path("coco_sample", model, 1, dims, "cpu", 0)
|
| 112 |
+
m2, s2 = compute_statistics_of_path("gen_sample", model, 1, dims, "cpu", 0)
|
| 113 |
+
|
| 114 |
+
fid = calculate_frechet_distance(m1, s1, m2, s2)
|
| 115 |
+
print(f"FID: {fid:.4f}")
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
Expect a `LinAlgWarning: Matrix is singular` at low sample counts (n < 2048) — this is expected, not a bug; see the caveat in `eval.md`.
|
| 119 |
+
|
| 120 |
+
## 6. Compute CLIP Score
|
| 121 |
+
|
| 122 |
+
```python
|
| 123 |
+
# compute_clip_score.py
|
| 124 |
+
import json
|
| 125 |
+
import torch
|
| 126 |
+
from PIL import Image
|
| 127 |
+
from transformers import CLIPModel, CLIPProcessor
|
| 128 |
+
|
| 129 |
+
with open("coco_sample/captions.json") as f:
|
| 130 |
+
meta = json.load(f)
|
| 131 |
+
|
| 132 |
+
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
|
| 133 |
+
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
|
| 134 |
+
model.eval()
|
| 135 |
+
|
| 136 |
+
scores = []
|
| 137 |
+
for i, row in enumerate(meta):
|
| 138 |
+
img = Image.open(f"gen_sample/gen_{i:03d}.png").convert("RGB")
|
| 139 |
+
inputs = processor(text=[row["caption"]], images=img, return_tensors="pt", padding=True, truncation=True)
|
| 140 |
+
with torch.no_grad():
|
| 141 |
+
out = model(**inputs)
|
| 142 |
+
img_emb = out.image_embeds / out.image_embeds.norm(dim=-1, keepdim=True)
|
| 143 |
+
txt_emb = out.text_embeds / out.text_embeds.norm(dim=-1, keepdim=True)
|
| 144 |
+
cos_sim = (img_emb * txt_emb).sum(dim=-1).item()
|
| 145 |
+
scores.append(max(0.0, 100 * cos_sim))
|
| 146 |
+
|
| 147 |
+
print(f"Mean CLIP Score (n={len(scores)}): {sum(scores)/len(scores):.4f}")
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
First run downloads `openai/clip-vit-base-patch32` (~600MB) from the Hugging Face Hub.
|
| 151 |
+
|
| 152 |
+
## Notes / gotchas hit while building this
|
| 153 |
+
|
| 154 |
+
- **Disk location matters.** All of the above (venv, pip cache, HF cache, downloaded datasets) should point at a drive with several GB free — `torch`+`torchvision`+`transformers`+CLIP+Inception weights add up to ~1.5–2GB. Set `PIP_CACHE_DIR`, `HF_HOME`, and `TORCH_HOME` env vars if your default drive is tight on space.
|
| 155 |
+
- **Sample size is the biggest caveat.** n=40 is fast and cheap but gives a noisy FID (singular covariance matrix) and a noisy CLIP Score mean. Scaling toward the standard n=30,000 COCO FID protocol means re-running steps 3–6 with a bigger `N`, which will take proportionally longer and use proportionally more disk/bandwidth.
|
eval.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# PixelModel — Evaluation
|
| 2 |
+
|
| 3 |
+
## ⚠️ Read this before the numbers
|
| 4 |
+
|
| 5 |
+
PixelModel is explicitly a toy (see `README.md`: *"It's a toy. It's not useful. But it works."*). It is a **~203K-parameter, 3-layer MLP** with no text encoder, no VAE, and no diffusion/transformer backbone — the entire "model" is 3 weight matrices encoded as pixel values in `model.png`. It was trained on 6 solid-color 32×32 swatches (red/green/blue/white/yellow/dark) using a deterministic char-sum prompt embedding.
|
| 6 |
+
|
| 7 |
+
It **cannot do general text-to-image generation**. Prompts outside its 6 training targets don't produce anything resembling the caption — they produce noisy, mostly-yellow-biased 32×32 blocks (see `gen_sample/` outputs below). The numbers in this file are real, measured results, not placeholders — and they reflect that reality: this model is far outside the distribution of real photos and does not respond meaningfully to prompt content.
|
| 8 |
+
|
| 9 |
+
This eval exists so the leaderboard entry reflects an honest, reproducible measurement rather than fabricated scores.
|
| 10 |
+
|
| 11 |
+
## Parameter count
|
| 12 |
+
|
| 13 |
+
No bias terms; only 3 weight matrices, all stored in `model.png`:
|
| 14 |
+
|
| 15 |
+
| Layer | Shape | Params |
|
| 16 |
+
|---|---|---|
|
| 17 |
+
| W1 (prompt→hidden) | 64×32 | 2,048 |
|
| 18 |
+
| W2 (hidden→hidden) | 64×64 | 4,096 |
|
| 19 |
+
| W3 (hidden→output) | 3072×64 | 196,608 |
|
| 20 |
+
| **Total** | | **202,752 (~0.2M)** |
|
| 21 |
+
|
| 22 |
+
No separate text encoder or VAE — the char-sum embedding and the MLP above are the entire pipeline. Well under the 1.5B ceiling.
|
| 23 |
+
|
| 24 |
+
## Method
|
| 25 |
+
|
| 26 |
+
- **Real captions/images**: 40 real (image, caption) pairs streamed from [`sayakpaul/coco-30-val-2014`](https://huggingface.co/datasets/sayakpaul/coco-30-val-2014) (a public 30K-sample subset of MS-COCO val2014), used as-is — no cherry-picking, first 40 rows of the stream.
|
| 27 |
+
- **Generation**: ran `model.forward()` (same code as `main.py`) on each of the 40 real captions to produce 40×32×32 RGB images.
|
| 28 |
+
- **FID**: standard InceptionV3 pool3 (2048-dim) features via `pytorch-fid`, comparing the 40 real COCO images against the 40 generated images.
|
| 29 |
+
- **CLIP Score**: `openai/clip-vit-base-patch32`, cosine similarity between each generated image and the caption used to generate it, scaled ×100, averaged over the 40 samples.
|
| 30 |
+
|
| 31 |
+
Full scripts are not bundled in this repo (they live outside it, in a throwaway eval env) — the method above is sufficient to reproduce: stream N real COCO pairs, run `main.py`-equivalent inference, feed both sets through InceptionV3 for FID and through CLIP for CLIP Score.
|
| 32 |
+
|
| 33 |
+
### Important caveat on sample size
|
| 34 |
+
|
| 35 |
+
Standard COCO FID evaluations use ~30,000 samples because FID's covariance-matrix estimate needs that scale to be stable (it's a 2048×2048 covariance matrix). **This run used n=40**, chosen to keep the eval fast and disk-light given local constraints. At n=40 the covariance matrix is singular (rank ≤ 40 ≪ 2048); `pytorch-fid` raised a `LinAlgWarning: Matrix is singular` when computing it. The FID number below is therefore a **rough, high-variance, directional signal**, not a publication-grade FID — treat it as "wildly out of distribution" rather than a precise score. CLIP Score is a per-sample metric (no covariance estimation) so it doesn't have this specific problem, but n=40 is still a small sample for a stable mean.
|
| 36 |
+
|
| 37 |
+
## Results
|
| 38 |
+
|
| 39 |
+
| Metric | Value | Context |
|
| 40 |
+
|---|---|---|
|
| 41 |
+
| FID (n=40) | **566.84** | Competitive T2I models score ~10–30 on full COCO FID; scores in the hundreds indicate the generated images are essentially out of the real-photo distribution entirely. |
|
| 42 |
+
| CLIP Score (n=40) | **18.60** (cosine 0.186) | Competitive T2I models score ~28–32; ~15–20 is roughly the floor for images with little-to-no real semantic correspondence to the prompt. |
|
| 43 |
+
|
| 44 |
+
## Leaderboard entry
|
| 45 |
+
|
| 46 |
+
```js
|
| 47 |
+
{
|
| 48 |
+
name: "PixelModel",
|
| 49 |
+
org: "bench-labs",
|
| 50 |
+
params: "0.2M",
|
| 51 |
+
fid: 566.84,
|
| 52 |
+
clipScore: 0.186,
|
| 53 |
+
resolution: "32x32",
|
| 54 |
+
releaseDate: "2026-07-14",
|
| 55 |
+
links: {
|
| 56 |
+
card: "https://huggingface.co/bench-labs/pixelmodel"
|
| 57 |
+
}
|
| 58 |
+
},
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
*(`clipScore` given as the 0–1 cosine value per the template's field description; the 0–100 CLIP Score above is the more commonly reported form of the same number.)*
|