AbstractPhil commited on
Commit
abb7e9d
Β·
verified Β·
1 Parent(s): 71edcab

Create freckles_256_trainer.py

Browse files
Files changed (1) hide show
  1. freckles_256_trainer.py +333 -0
freckles_256_trainer.py ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Freckles High-Resolution Noise Training
3
+ =========================================
4
+ Same 2.5M param model (V=48, D=4, ps=4), scaled to larger images.
5
+ Initialized from v40 Freckles 64Γ—64 weights β€” patch-level weights transfer directly.
6
+
7
+ 256Γ—256: 4096 patches (64Γ—64 grid) β€” batch=16
8
+ 512Γ—512: 16384 patches (128Γ—128 grid) β€” batch=4
9
+
10
+ Cross-attention over N patches is O(NΒ²). Batch sizes adjusted accordingly.
11
+ """
12
+
13
+ import os
14
+ import math
15
+ import time
16
+ import torch
17
+ import torch.nn as nn
18
+ import torch.nn.functional as F
19
+ import numpy as np
20
+ from tqdm import tqdm
21
+
22
+ try:
23
+ from google.colab import userdata
24
+ os.environ["HF_TOKEN"] = userdata.get('HF_TOKEN')
25
+ from huggingface_hub import login
26
+ login(token=os.environ["HF_TOKEN"])
27
+ except Exception:
28
+ pass
29
+
30
+
31
+ # ═══════════════════════════════════════════════════════════════
32
+ # NOISE GENERATORS (16 types)
33
+ # ═══════════════════════════════════════════════════════════════
34
+
35
+ def _pink(shape):
36
+ w = torch.randn(shape)
37
+ S = torch.fft.rfft2(w)
38
+ h, ww = shape[-2], shape[-1]
39
+ fy = torch.fft.fftfreq(h).unsqueeze(-1).expand(-1, ww // 2 + 1)
40
+ fx = torch.fft.rfftfreq(ww).unsqueeze(0).expand(h, -1)
41
+ return torch.fft.irfft2(S / torch.sqrt(fx**2 + fy**2).clamp(min=1e-8), s=(h, ww))
42
+
43
+ def _brown(shape):
44
+ w = torch.randn(shape)
45
+ S = torch.fft.rfft2(w)
46
+ h, ww = shape[-2], shape[-1]
47
+ fy = torch.fft.fftfreq(h).unsqueeze(-1).expand(-1, ww // 2 + 1)
48
+ fx = torch.fft.rfftfreq(ww).unsqueeze(0).expand(h, -1)
49
+ return torch.fft.irfft2(S / (fx**2 + fy**2).clamp(min=1e-8), s=(h, ww))
50
+
51
+ def _gen_noise(t, s, rng):
52
+ if t == 0: return torch.randn(3, s, s)
53
+ elif t == 1: return torch.rand(3, s, s) * 2 - 1
54
+ elif t == 2: return (torch.rand(3, s, s) - 0.5) * 4
55
+ elif t == 3:
56
+ lam = rng.uniform(0.5, 20.0)
57
+ return torch.poisson(torch.full((3, s, s), lam)) / lam - 1.0
58
+ elif t == 4:
59
+ img = _pink((3, s, s)); return img / (img.std() + 1e-8)
60
+ elif t == 5:
61
+ img = _brown((3, s, s)); return img / (img.std() + 1e-8)
62
+ elif t == 6:
63
+ return torch.where(torch.rand(3, s, s) > 0.5,
64
+ torch.ones(3, s, s) * 2, -torch.ones(3, s, s) * 2) + torch.randn(3, s, s) * 0.1
65
+ elif t == 7:
66
+ return torch.randn(3, s, s) * (torch.rand(3, s, s) > 0.9).float() * 3
67
+ elif t == 8:
68
+ b = rng.randint(2, max(3, s // 4))
69
+ sm = torch.randn(3, s // b + 1, s // b + 1)
70
+ return F.interpolate(sm.unsqueeze(0), size=s, mode='nearest').squeeze(0)
71
+ elif t == 9:
72
+ gy = torch.linspace(-2, 2, s).unsqueeze(1).expand(s, s)
73
+ gx = torch.linspace(-2, 2, s).unsqueeze(0).expand(s, s)
74
+ a = rng.uniform(0, 2 * math.pi)
75
+ return (math.cos(a) * gx + math.sin(a) * gy).unsqueeze(0).expand(3, -1, -1) + torch.randn(3, s, s) * 0.5
76
+ elif t == 10:
77
+ cs = rng.randint(2, max(3, s // 4))
78
+ cy = torch.arange(s) // cs; cx = torch.arange(s) // cs
79
+ return ((cy.unsqueeze(1) + cx.unsqueeze(0)) % 2).float().unsqueeze(0).expand(3, -1, -1) * 2 - 1 + torch.randn(3, s, s) * 0.3
80
+ elif t == 11:
81
+ alpha = rng.uniform(0.2, 0.8)
82
+ return alpha * torch.randn(3, s, s) + (1 - alpha) * (torch.rand(3, s, s) * 2 - 1)
83
+ elif t == 12:
84
+ img = torch.zeros(3, s, s); h2 = s // 2; w2 = s // 2
85
+ img[:, :h2, :w2] = torch.randn(3, h2, w2)
86
+ img[:, :h2, w2:s] = torch.rand(3, h2, s - w2) * 2 - 1
87
+ img[:, h2:s, :w2] = _pink((3, s - h2, w2)) / 2
88
+ img[:, h2:s, w2:s] = torch.where(torch.rand(3, s - h2, s - w2) > 0.5,
89
+ torch.ones(3, s - h2, s - w2), -torch.ones(3, s - h2, s - w2))
90
+ return img
91
+ elif t == 13:
92
+ return torch.tan(math.pi * (torch.rand(3, s, s) - 0.5)).clamp(-3, 3)
93
+ elif t == 14:
94
+ return torch.empty(3, s, s).exponential_(1.0) - 1.0
95
+ elif t == 15:
96
+ u = torch.rand(3, s, s) - 0.5
97
+ return -torch.sign(u) * torch.log1p(-2 * u.abs())
98
+ return torch.randn(3, s, s)
99
+
100
+
101
+ class OmegaNoiseDataset(torch.utils.data.Dataset):
102
+ def __init__(self, size=500000, img_size=256):
103
+ self.size = size
104
+ self.img_size = img_size
105
+ self._rng = np.random.RandomState(42)
106
+ self._call_count = 0
107
+ def __len__(self):
108
+ return self.size
109
+ def __getitem__(self, idx):
110
+ self._call_count += 1
111
+ if self._call_count % 1000 == 0:
112
+ self._rng = np.random.RandomState(int.from_bytes(os.urandom(4), 'big'))
113
+ torch.manual_seed(int.from_bytes(os.urandom(4), 'big'))
114
+ noise_type = idx % 16
115
+ img = _gen_noise(noise_type, self.img_size, self._rng).clamp(-4, 4)
116
+ return img.float(), noise_type
117
+
118
+
119
+ NOISE_NAMES = {
120
+ 0: 'gaussian', 1: 'uniform', 2: 'uniform_sc', 3: 'poisson',
121
+ 4: 'pink', 5: 'brown', 6: 'salt_pepper', 7: 'sparse',
122
+ 8: 'block', 9: 'gradient', 10: 'checker', 11: 'mixed',
123
+ 12: 'structural', 13: 'cauchy', 14: 'exponential', 15: 'laplace',
124
+ }
125
+
126
+
127
+ # ═══════════════════════════════════════════════════════════════
128
+ # PER-TYPE EVAL
129
+ # ═══════════════════════════════════════════════════════════════
130
+
131
+ def eval_per_type(model, img_size, device, n_per=16):
132
+ rng = np.random.RandomState(99)
133
+ model.eval()
134
+ results = {}
135
+ with torch.no_grad():
136
+ for t in range(16):
137
+ imgs = torch.stack([_gen_noise(t, img_size, rng).clamp(-4, 4)
138
+ for _ in range(n_per)]).to(device)
139
+ out = model(imgs)
140
+ results[t] = F.mse_loss(out['recon'], imgs).item()
141
+ return results
142
+
143
+
144
+ # ═══════════════════════════════════════════════════════════════
145
+ # TRAINING
146
+ # ═══════════════════════════════════════════════════════════════
147
+
148
+ PRESETS = {
149
+ '256': dict(
150
+ img_size=256,
151
+ batch_size=64,
152
+ ds_size=1280000,
153
+ val_size=12800,
154
+ epochs=1,
155
+ lr=1e-4,
156
+ hf_version='v41_freckles_256',
157
+ save_every=1,
158
+ ),
159
+ '512': dict(
160
+ img_size=512,
161
+ batch_size=12,
162
+ ds_size=1280000,
163
+ val_size=12800,
164
+ epochs=1,
165
+ lr=1e-4,
166
+ hf_version='v42_freckles_512',
167
+ save_every=1,
168
+ ),
169
+ }
170
+
171
+
172
+ def train(preset='256', device='cuda'):
173
+ from geolip_svae import load_model
174
+
175
+ cfg = PRESETS[preset]
176
+ img_size = cfg['img_size']
177
+ batch_size = cfg['batch_size']
178
+ epochs = cfg['epochs']
179
+ lr = cfg['lr']
180
+ hf_version = cfg['hf_version']
181
+ save_every = cfg['save_every']
182
+ ps = 4
183
+
184
+ device = torch.device(device if torch.cuda.is_available() else 'cpu')
185
+ n_patches = (img_size // ps) ** 2
186
+
187
+ print("\n" + "=" * 70)
188
+ print(f"FRECKLES {img_size}Γ—{img_size} β€” High-Resolution Noise Training")
189
+ print("=" * 70)
190
+
191
+ # ── Load from v40 Freckles ──
192
+ print(" Loading Freckles v40 (64Γ—64 trained)...")
193
+ model, base_cfg = load_model(hf_version='v40_freckles_noise', device=device)
194
+ model.train()
195
+ for p in model.parameters():
196
+ p.requires_grad = True
197
+
198
+ n_params = sum(p.numel() for p in model.parameters())
199
+ print(f" Params: {n_params:,} (from v40, trainable)")
200
+ print(f" Resolution: {img_size}Γ—{img_size}")
201
+ print(f" Patches: {n_patches} ({img_size//ps}Γ—{img_size//ps} grid)")
202
+ print(f" SVD: ({base_cfg['V']},{base_cfg['D']}), ps={ps}")
203
+ print(f" Batch: {batch_size}, lr={lr}, epochs={epochs}")
204
+ print(f" Cross-attn sequence length: {n_patches}")
205
+ print(f" Estimated attn memory: ~{n_patches**2 * 2 * 4 / 1e6:.0f}MB per sample")
206
+ print("=" * 70)
207
+
208
+ opt = torch.optim.Adam(model.parameters(), lr=lr)
209
+ sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=epochs)
210
+
211
+ train_ds = OmegaNoiseDataset(size=cfg['ds_size'], img_size=img_size)
212
+ val_ds = OmegaNoiseDataset(size=cfg['val_size'], img_size=img_size)
213
+ train_loader = torch.utils.data.DataLoader(
214
+ train_ds, batch_size=batch_size, shuffle=True,
215
+ num_workers=8, pin_memory=True, drop_last=True)
216
+ val_loader = torch.utils.data.DataLoader(
217
+ val_ds, batch_size=batch_size, shuffle=False,
218
+ num_workers=2, pin_memory=True)
219
+
220
+ save_dir = f'/content/freckles_{img_size}_checkpoints'
221
+ hf_repo = 'AbstractPhil/geolip-SVAE'
222
+ os.makedirs(save_dir, exist_ok=True)
223
+
224
+ hf_enabled = False
225
+ api = None
226
+ try:
227
+ from huggingface_hub import HfApi
228
+ api = HfApi(); api.whoami(); hf_enabled = True
229
+ print(f" HuggingFace: {hf_repo}/{hf_version}")
230
+ except:
231
+ pass
232
+
233
+ best_mse = float('inf')
234
+ D = base_cfg['D']
235
+
236
+ for epoch in range(1, epochs + 1):
237
+ model.train()
238
+ total_loss, total_recon, n = 0, 0, 0
239
+ t0 = time.time()
240
+
241
+ pbar = tqdm(train_loader, desc=f"Ep {epoch}/{epochs}",
242
+ bar_format='{l_bar}{bar:20}{r_bar}')
243
+ for batch_idx, (images, _) in enumerate(pbar):
244
+ images = images.to(device)
245
+ opt.zero_grad()
246
+ out = model(images)
247
+ recon_loss = F.mse_loss(out['recon'], images)
248
+
249
+ # Pure recon loss β€” no CV penalty (Freckles doesn't need it)
250
+ loss = recon_loss
251
+ loss.backward()
252
+ torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
253
+ opt.step()
254
+
255
+ total_loss += loss.item() * len(images)
256
+ total_recon += recon_loss.item() * len(images)
257
+ n += len(images)
258
+ pbar.set_postfix_str(f"mse={recon_loss.item():.6f}")
259
+
260
+ sched.step()
261
+ epoch_time = time.time() - t0
262
+
263
+ # ── Eval ──
264
+ model.eval()
265
+ val_mse, val_n = 0, 0
266
+ with torch.no_grad():
267
+ for imgs, _ in val_loader:
268
+ imgs = imgs.to(device)
269
+ out = model(imgs)
270
+ val_mse += F.mse_loss(out['recon'], imgs).item() * len(imgs)
271
+ val_n += len(imgs)
272
+ val_mse /= val_n
273
+
274
+ # Geometry
275
+ with torch.no_grad():
276
+ sample = next(iter(val_loader))[0][:min(8, batch_size)].to(device)
277
+ out = model(sample)
278
+ S_mean = out['svd']['S'].mean(dim=(0, 1))
279
+ erank = model.effective_rank(out['svd']['S'].reshape(-1, D)).mean().item()
280
+
281
+ # Per-type (smaller sample for speed at high res)
282
+ type_mse = eval_per_type(model, img_size, device, n_per=8)
283
+ type_str = " ".join(f"{NOISE_NAMES[t][:4]}={v:.4f}" for t, v in sorted(type_mse.items()))
284
+
285
+ print(f" ep{epoch:3d} | recon={total_recon/n:.6f} val={val_mse:.6f} | "
286
+ f"S0={S_mean[0]:.3f} SD={S_mean[-1]:.3f} er={erank:.2f} | {epoch_time:.0f}s")
287
+ print(f" {type_str}")
288
+
289
+ # ── Checkpoint ──
290
+ ckpt = {
291
+ 'epoch': epoch, 'val_mse': val_mse,
292
+ 'model_state_dict': model.state_dict(),
293
+ 'config': {
294
+ 'V': base_cfg['V'], 'D': D, 'patch_size': ps,
295
+ 'hidden': base_cfg['hidden'], 'depth': base_cfg['depth'],
296
+ 'n_cross_layers': base_cfg['n_cross_layers'],
297
+ 'n_heads': 2, 'smooth_mid': 8,
298
+ 'img_size': img_size,
299
+ },
300
+ }
301
+
302
+ if val_mse < best_mse:
303
+ best_mse = val_mse
304
+ torch.save(ckpt, os.path.join(save_dir, 'best.pt'))
305
+
306
+ if epoch % save_every == 0:
307
+ path = os.path.join(save_dir, f'epoch_{epoch:04d}.pt')
308
+ torch.save(ckpt, path)
309
+ if hf_enabled:
310
+ try:
311
+ api.upload_file(path_or_fileobj=path,
312
+ path_in_repo=f"{hf_version}/checkpoints/{os.path.basename(path)}",
313
+ repo_id=hf_repo, repo_type="model")
314
+ api.upload_file(path_or_fileobj=os.path.join(save_dir, 'best.pt'),
315
+ path_in_repo=f"{hf_version}/checkpoints/best.pt",
316
+ repo_id=hf_repo, repo_type="model")
317
+ print(f" ☁️ Uploaded ep{epoch}")
318
+ except Exception as e:
319
+ print(f" ⚠️ Upload: {e}")
320
+
321
+ print(f"\n FRECKLES {img_size}Γ—{img_size} COMPLETE")
322
+ print(f" Best val MSE: {best_mse:.6f}")
323
+ return model
324
+
325
+
326
+ if __name__ == "__main__":
327
+ import sys
328
+ torch.set_float32_matmul_precision('high')
329
+
330
+ # CLI: python freckles_hires.py 256
331
+ # Colab: just set PRESET below
332
+ PRESET = '256' # ← change to '512' for the other run
333
+ train(preset=PRESET)