AbstractPhil commited on
Commit
d1ff9c6
Β·
verified Β·
1 Parent(s): cbbe133

Create train_prototype_cell_cifar_10.py

Browse files
Files changed (1) hide show
  1. train_prototype_cell_cifar_10.py +222 -0
train_prototype_cell_cifar_10.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ SpectralCell Diamond β€” Maximum Performance Sweep
3
+ ===================================================
4
+ Data augmentation + conv spatial head + CE only.
5
+ Sweep D=4 and D=16 back-to-back.
6
+
7
+ 32Γ—32 CIFAR-10 β†’ 16 patches (ps=8) β†’ token_dim=192
8
+ Augmentation: RandomHorizontalFlip + RandomCrop(32, pad=4)
9
+ """
10
+
11
+ import torch
12
+ import torch.nn as nn
13
+ import torch.nn.functional as F
14
+ import time
15
+ from tqdm import tqdm
16
+ import torchvision
17
+ import torchvision.transforms as T
18
+
19
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
20
+
21
+ def extract_patches(images, ps):
22
+ B, C, H, W = images.shape
23
+ gh, gw = H // ps, W // ps
24
+ p = images.reshape(B, C, gh, ps, gw, ps)
25
+ p = p.permute(0, 2, 4, 1, 3, 5)
26
+ return p.reshape(B, gh * gw, C * ps * ps), gh, gw
27
+
28
+ PS = 8
29
+ IMG_SIZE = 32
30
+ GH, GW = IMG_SIZE // PS, IMG_SIZE // PS # 4, 4
31
+ TOKEN_DIM = 3 * PS * PS # 192
32
+ N_CLASSES = 10
33
+ CLASSES = ['airplane','auto','bird','cat','deer','dog','frog','horse','ship','truck']
34
+
35
+
36
+ class ConvSpatialHead(nn.Module):
37
+ """Conv on 4Γ—4 grid of formatted tokens.
38
+ token_dim channels β†’ compress β†’ spatial reasoning β†’ classify.
39
+ """
40
+ def __init__(self, token_dim, gh, gw, n_classes=10):
41
+ super().__init__()
42
+ self.gh = gh
43
+ self.gw = gw
44
+ self.conv = nn.Sequential(
45
+ nn.Conv2d(token_dim, 64, 3, padding=1),
46
+ nn.BatchNorm2d(64),
47
+ nn.GELU(),
48
+ nn.Conv2d(64, 64, 3, padding=1),
49
+ nn.BatchNorm2d(64),
50
+ nn.GELU(),
51
+ nn.Conv2d(64, 32, 3, padding=1),
52
+ nn.BatchNorm2d(32),
53
+ nn.GELU(),
54
+ nn.AdaptiveAvgPool2d(1),
55
+ )
56
+ self.head = nn.Linear(32, n_classes)
57
+
58
+ def forward(self, tokens):
59
+ B = tokens.shape[0]
60
+ spatial = tokens.permute(0, 2, 1).reshape(B, -1, self.gh, self.gw)
61
+ h = self.conv(spatial).squeeze(-1).squeeze(-1)
62
+ return self.head(h)
63
+
64
+
65
+ # ── Data with augmentation ───────────────────────────────────────
66
+
67
+ train_transform = T.Compose([
68
+ T.RandomCrop(32, padding=4),
69
+ T.RandomHorizontalFlip(),
70
+ T.ToTensor(),
71
+ ])
72
+ test_transform = T.Compose([T.ToTensor()])
73
+
74
+ cifar_train = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=train_transform)
75
+ cifar_test = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=test_transform)
76
+
77
+ train_loader = torch.utils.data.DataLoader(
78
+ cifar_train, batch_size=256, shuffle=True, num_workers=4, pin_memory=True, drop_last=True)
79
+ test_loader = torch.utils.data.DataLoader(
80
+ cifar_test, batch_size=256, shuffle=False, num_workers=4, pin_memory=True)
81
+
82
+
83
+ # ── Sweep ────────────────────────────────────────────────────────
84
+
85
+ CONFIGS = [
86
+ ("D=4, V=16, h=128", 16, 4, 128, 2, 1, 2),
87
+ ("D=16, V=16, h=128", 16, 16, 128, 2, 1, 2),
88
+ ]
89
+
90
+ EPOCHS = 50
91
+ results = {}
92
+
93
+ for name, V, D, HIDDEN, DEPTH, N_CROSS, N_HEADS in CONFIGS:
94
+
95
+ # Adjust n_heads for D
96
+ if D < N_HEADS * 2:
97
+ N_HEADS = max(1, D // 2)
98
+
99
+ print(f"\n{'═' * 60}")
100
+ print(f" {name}")
101
+ print(f"{'═' * 60}")
102
+
103
+ cell = SpectralCell(
104
+ token_dim=TOKEN_DIM, V=V, D=D,
105
+ hidden=HIDDEN, depth=DEPTH, n_cross=N_CROSS,
106
+ n_heads=N_HEADS, max_alpha=0.2,
107
+ ).to(device)
108
+
109
+ clf_head = ConvSpatialHead(TOKEN_DIM, GH, GW, N_CLASSES).to(device)
110
+
111
+ cell.summary()
112
+ n_cell = sum(p.numel() for p in cell.parameters())
113
+ n_head = sum(p.numel() for p in clf_head.parameters())
114
+ n_total = n_cell + n_head
115
+ print(f" Cell: {n_cell:,} Head: {n_head:,} Total: {n_total:,}")
116
+ print(f" Augmentation: RandomCrop(32, pad=4) + HFlip")
117
+
118
+ all_params = list(cell.parameters()) + list(clf_head.parameters())
119
+ opt = torch.optim.Adam(all_params, lr=3e-4)
120
+ sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=EPOCHS)
121
+
122
+ best_acc = 0
123
+ t0 = time.time()
124
+
125
+ for epoch in range(1, EPOCHS + 1):
126
+ cell.train()
127
+ clf_head.train()
128
+ correct, total = 0, 0
129
+
130
+ for images, labels in tqdm(train_loader, desc=f"Ep {epoch:2d}", leave=False):
131
+ images, labels = images.to(device), labels.to(device)
132
+ patches, _, _ = extract_patches(images, PS)
133
+ result = cell.format(patches)
134
+ logits = clf_head(result['output'])
135
+ loss = F.cross_entropy(logits, labels)
136
+ opt.zero_grad()
137
+ loss.backward()
138
+ opt.step()
139
+ correct += (logits.argmax(-1) == labels).sum().item()
140
+ total += images.shape[0]
141
+
142
+ sched.step()
143
+ train_acc = correct / total
144
+
145
+ cell.eval()
146
+ clf_head.eval()
147
+ val_correct, val_total = 0, 0
148
+ pcc = torch.zeros(10)
149
+ pct = torch.zeros(10)
150
+ last_S_orig, last_S = None, None
151
+
152
+ with torch.no_grad():
153
+ for images, labels in test_loader:
154
+ images, labels = images.to(device), labels.to(device)
155
+ patches, _, _ = extract_patches(images, PS)
156
+ result = cell.format(patches)
157
+ logits = clf_head(result['output'])
158
+ preds = logits.argmax(-1)
159
+ val_correct += (preds == labels).sum().item()
160
+ val_total += images.shape[0]
161
+ for c in range(10):
162
+ m = labels == c
163
+ pcc[c] += (preds[m] == labels[m]).sum().item()
164
+ pct[c] += m.sum().item()
165
+ last_S_orig = result['S_orig']
166
+ last_S = result['S']
167
+
168
+ val_acc = val_correct / val_total
169
+ star = ''
170
+ if val_acc > best_acc:
171
+ best_acc = val_acc
172
+ star = ' β˜…'
173
+
174
+ S_mean = last_S_orig.mean(dim=(0, 1))
175
+ erank = cell.effective_rank(last_S_orig.reshape(-1, D)).mean().item()
176
+ shift = cell.spectral_shift(last_S_orig, last_S)
177
+
178
+ if epoch <= 3 or epoch % 5 == 0 or epoch == EPOCHS:
179
+ s_str = ', '.join(f'{v:.3f}' for v in S_mean.tolist()[:4])
180
+ if D > 4:
181
+ s_str += f', ... {S_mean[-1]:.3f}'
182
+ print(f" ep{epoch:3d} acc={val_acc:.1%}{star} train={train_acc:.1%} "
183
+ f"S=[{s_str}] erank={erank:.2f}")
184
+
185
+ if epoch <= 2 or epoch % 10 == 0 or epoch == EPOCHS:
186
+ pca = pcc / (pct + 1e-8)
187
+ print(f" Per-class:")
188
+ for c in range(10):
189
+ bar = 'β–ˆ' * int(pca[c] * 20)
190
+ print(f" {CLASSES[c]:<10s} {pca[c]:5.1%} {bar}")
191
+
192
+ elapsed = time.time() - t0
193
+ results[name] = (best_acc, n_total, elapsed)
194
+ print(f"\n β†’ Best: {best_acc:.1%} | {n_total:,} params | {elapsed:.0f}s")
195
+
196
+
197
+ # ── Scoreboard ───────────────────────────────────────────────────
198
+
199
+ print(f"\n{'═' * 60}")
200
+ print(f" DIAMOND SCOREBOARD")
201
+ print(f"{'═' * 60}")
202
+
203
+ print(f"\n {'Config':<45s} {'Params':>8s} {'Acc':>8s} {'Time':>6s}")
204
+ print(f" {'-' * 69}")
205
+
206
+ # Previous results
207
+ prev = [
208
+ ("Recon only (MSE, no head)", "199K", "β€”"),
209
+ ("CE only + mean pool D=4", "212K", "55.1%"),
210
+ ("MSE+CE + mean pool D=4", "212K", "55.7%"),
211
+ ("CE only + mean pool D=16", "263K", "56.3%"),
212
+ ]
213
+ for n, p, a in prev:
214
+ print(f" {n:<45s} {p:>8s} {a:>8s}")
215
+
216
+ print(f" {'-' * 69}")
217
+
218
+ for name, (acc, params, elapsed) in sorted(results.items(), key=lambda x: x[1][0]):
219
+ print(f" {name + ' + conv + aug':<45s} {params:>8,} {acc:>7.1%} {elapsed:>5.0f}s")
220
+
221
+ best = max(results.items(), key=lambda x: x[1][0])
222
+ print(f"\n Best: {best[0]} β†’ {best[1][0]:.1%}")