Costi Claude Sonnet 5 commited on
Commit
d9a3537
·
1 Parent(s): 1d88bd9

Add real eval results and reproduction guide

Browse files

Runs 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>

Files changed (2) hide show
  1. EVAL_REPRODUCTION.md +155 -0
  2. 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.)*