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

Create train_prototype_conduit_battery_cifar_10.py

Browse files
train_prototype_conduit_battery_cifar_10.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Train ConduitBattery β€” CIFAR-10 Classification
3
+ =================================================
4
+ Conv pathway: (B, 3, 32, 32) β†’ 64 groups of 16Γ—16 local matrices
5
+ SVD with conduit telemetry on each group.
6
+ Two-stream relational processing: geometric + content with FiLM.
7
+ Classify from global_token (pooled geometric summary).
8
+
9
+ Config: input_dim=3, rank=16, conv_window=4, geom_dim=32, model_dim=64
10
+ CE loss, augmentation.
11
+ """
12
+
13
+ import torch
14
+ import torch.nn as nn
15
+ import torch.nn.functional as F
16
+ import time
17
+ from tqdm import tqdm
18
+ import torchvision
19
+ import torchvision.transforms as T
20
+
21
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
22
+
23
+ # ── Config ───────────────────────────────────────────────────────
24
+
25
+ N_CLASSES = 10
26
+ EPOCHS = 50
27
+ BATCH = 256
28
+ LR = 3e-4
29
+ CLASSES = ['airplane','auto','bird','cat','deer','dog','frog','horse','ship','truck']
30
+
31
+ battery_config = BaseConfig(
32
+ input_dim=3,
33
+ model_dim=64,
34
+ out_dim=3,
35
+ rank=16,
36
+ geom_dim=32,
37
+ relation_depth=2,
38
+ num_heads=4,
39
+ mlp_ratio=2.0,
40
+ dropout=0.0,
41
+ conv_window=4,
42
+ max_spectral_shift=0.20,
43
+ max_scale_shift=0.10,
44
+ residual_init=0.0,
45
+ use_row_norm=True,
46
+ use_conduit=True,
47
+ compute_dtype="fp64",
48
+ )
49
+
50
+ # ── Classification Heads ─────────────────────────────────────────
51
+
52
+ class GlobalHead(nn.Module):
53
+ """Classify from global_token (B, model_dim) β†’ logits."""
54
+ def __init__(self, model_dim, n_classes=10):
55
+ super().__init__()
56
+ self.head = nn.Sequential(
57
+ nn.LayerNorm(model_dim),
58
+ nn.Linear(model_dim, n_classes),
59
+ )
60
+ def forward(self, global_token):
61
+ return self.head(global_token)
62
+
63
+
64
+ class GridConvHead(nn.Module):
65
+ """Classify from analysis_grid (B, model_dim, gh, gw) via small conv."""
66
+ def __init__(self, model_dim, n_classes=10):
67
+ super().__init__()
68
+ self.conv = nn.Sequential(
69
+ nn.Conv2d(model_dim, 64, 3, padding=1),
70
+ nn.BatchNorm2d(64),
71
+ nn.GELU(),
72
+ nn.Conv2d(64, 32, 3, padding=1),
73
+ nn.BatchNorm2d(32),
74
+ nn.GELU(),
75
+ nn.AdaptiveAvgPool2d(1),
76
+ )
77
+ self.head = nn.Linear(32, n_classes)
78
+
79
+ def forward(self, grid):
80
+ h = self.conv(grid).squeeze(-1).squeeze(-1)
81
+ return self.head(h)
82
+
83
+
84
+ # ── Data ─────────────────────────────────────────────────────────
85
+
86
+ train_transform = T.Compose([
87
+ T.RandomCrop(32, padding=4),
88
+ T.RandomHorizontalFlip(),
89
+ T.ToTensor(),
90
+ ])
91
+ test_transform = T.Compose([T.ToTensor()])
92
+
93
+ cifar_train = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=train_transform)
94
+ cifar_test = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=test_transform)
95
+
96
+ train_loader = torch.utils.data.DataLoader(
97
+ cifar_train, batch_size=BATCH, shuffle=True, num_workers=4, pin_memory=True, drop_last=True)
98
+ test_loader = torch.utils.data.DataLoader(
99
+ cifar_test, batch_size=BATCH, shuffle=False, num_workers=4, pin_memory=True)
100
+
101
+
102
+ # ── Build Model ──────────────────────────────────────────────────
103
+
104
+ battery = ConduitBattery(battery_config).to(device)
105
+ head_global = GlobalHead(battery_config.model_dim, N_CLASSES).to(device)
106
+ head_grid = GridConvHead(battery_config.model_dim, N_CLASSES).to(device)
107
+
108
+ n_battery = sum(p.numel() for p in battery.parameters())
109
+ n_global = sum(p.numel() for p in head_global.parameters())
110
+ n_grid = sum(p.numel() for p in head_grid.parameters())
111
+
112
+ print(f"ConduitBattery config:")
113
+ print(f" input_dim={battery_config.input_dim}, rank={battery_config.rank}")
114
+ print(f" conv_window={battery_config.conv_window} β†’ 8Γ—8=64 groups of 16Γ—16")
115
+ print(f" geom_dim={battery_config.geom_dim}, model_dim={battery_config.model_dim}")
116
+ print(f" relation_depth={battery_config.relation_depth}")
117
+ print(f" conduit={'ON' if battery_config.use_conduit else 'OFF'}")
118
+ print(f"\nParams:")
119
+ print(f" Battery: {n_battery:,}")
120
+ print(f" GlobalHead: {n_global:,}")
121
+ print(f" GridHead: {n_grid:,}")
122
+
123
+
124
+ # ═══════════════════════════════════════════════════════════════
125
+ # EXPERIMENT A: global_token β†’ classification
126
+ # ═══════════════════════════════════════════════════════════════
127
+
128
+ def run_experiment(battery, clf_head, head_name, get_logits_fn):
129
+ """Train and evaluate one configuration."""
130
+ all_params = list(battery.parameters()) + list(clf_head.parameters())
131
+ n_total = sum(p.numel() for p in all_params)
132
+ opt = torch.optim.Adam(all_params, lr=LR)
133
+ sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=EPOCHS)
134
+
135
+ print(f"\n{'═' * 60}")
136
+ print(f" {head_name} β€” {n_total:,} params")
137
+ print(f"{'═' * 60}")
138
+
139
+ best_acc = 0
140
+ t0 = time.time()
141
+
142
+ for epoch in range(1, EPOCHS + 1):
143
+ battery.train()
144
+ clf_head.train()
145
+ correct, total = 0, 0
146
+
147
+ for images, labels in tqdm(train_loader, desc=f"Ep {epoch:2d}", leave=False):
148
+ images, labels = images.to(device), labels.to(device)
149
+ result = battery(images, input_kind='conv', return_state=False)
150
+ logits = get_logits_fn(result, clf_head)
151
+ loss = F.cross_entropy(logits, labels)
152
+ opt.zero_grad()
153
+ loss.backward()
154
+ opt.step()
155
+ correct += (logits.argmax(-1) == labels).sum().item()
156
+ total += images.shape[0]
157
+
158
+ sched.step()
159
+ train_acc = correct / total
160
+
161
+ battery.eval()
162
+ clf_head.eval()
163
+ val_correct, val_total = 0, 0
164
+ pcc = torch.zeros(10)
165
+ pct = torch.zeros(10)
166
+
167
+ with torch.no_grad():
168
+ for images, labels in test_loader:
169
+ images, labels = images.to(device), labels.to(device)
170
+ result = battery(images, input_kind='conv')
171
+ logits = get_logits_fn(result, clf_head)
172
+ preds = logits.argmax(-1)
173
+ val_correct += (preds == labels).sum().item()
174
+ val_total += images.shape[0]
175
+ for c in range(10):
176
+ m = labels == c
177
+ pcc[c] += (preds[m] == labels[m]).sum().item()
178
+ pct[c] += m.sum().item()
179
+
180
+ val_acc = val_correct / val_total
181
+ star = ''
182
+ if val_acc > best_acc:
183
+ best_acc = val_acc
184
+ star = ' β˜…'
185
+
186
+ if epoch <= 3 or epoch % 5 == 0 or epoch == EPOCHS:
187
+ S = result['S']
188
+ S_mean = S.mean(dim=(0, 1))
189
+ s_str = ', '.join(f'{v:.3f}' for v in S_mean.tolist()[:4])
190
+ if S.shape[-1] > 4:
191
+ s_str += f', ... {S_mean[-1]:.3f}'
192
+ erank = result['effective_rank'].mean().item()
193
+ entropy = result['spectral_entropy'].mean().item()
194
+ shift = (result['S_shifted'] - result['S']).abs().mean().item()
195
+
196
+ print(f" ep{epoch:3d} acc={val_acc:.1%}{star} train={train_acc:.1%} "
197
+ f"S=[{s_str}] erank={erank:.2f} shift={shift:.5f}")
198
+
199
+ if epoch <= 2 or epoch % 10 == 0 or epoch == EPOCHS:
200
+ pca = pcc / (pct + 1e-8)
201
+ print(f" Per-class:")
202
+ for c in range(10):
203
+ bar = 'β–ˆ' * int(pca[c] * 20)
204
+ print(f" {CLASSES[c]:<10s} {pca[c]:5.1%} {bar}")
205
+
206
+ elapsed = time.time() - t0
207
+ print(f"\n β†’ {head_name}: Best={best_acc:.1%} | {n_total:,} params | {elapsed:.0f}s")
208
+ return best_acc, n_total, elapsed
209
+
210
+
211
+ # Run experiments
212
+ results = {}
213
+
214
+ # A: global_token β†’ Linear
215
+ battery_a = ConduitBattery(battery_config).to(device)
216
+ head_a = GlobalHead(battery_config.model_dim, N_CLASSES).to(device)
217
+ acc_a, params_a, time_a = run_experiment(
218
+ battery_a, head_a, "ConduitBattery + GlobalHead",
219
+ lambda r, h: h(r['global_token'])
220
+ )
221
+ results['global'] = (acc_a, params_a, time_a)
222
+
223
+ # B: analysis_grid β†’ Conv
224
+ battery_b = ConduitBattery(battery_config).to(device)
225
+ head_b = GridConvHead(battery_config.model_dim, N_CLASSES).to(device)
226
+ acc_b, params_b, time_b = run_experiment(
227
+ battery_b, head_b, "ConduitBattery + GridConvHead",
228
+ lambda r, h: h(r['analysis_grid'])
229
+ )
230
+ results['grid_conv'] = (acc_b, params_b, time_b)
231
+
232
+
233
+ # ── Scoreboard ───────────────────────────────────────────────────
234
+
235
+ print(f"\n{'═' * 60}")
236
+ print(f" SCOREBOARD")
237
+ print(f"{'═' * 60}")
238
+
239
+ print(f"\n {'Config':<50s} {'Params':>8s} {'Acc':>8s}")
240
+ print(f" {'-' * 68}")
241
+
242
+ # SpectralCell baselines
243
+ prev = [
244
+ ("SpectralCell CE+mean D=4", "212K", "55.1%"),
245
+ ("SpectralCell CE+mean D=16", "263K", "56.3%"),
246
+ ("SpectralCell CE+conv D=4 +aug", "366K", "75.7%"),
247
+ ("SpectralCell CE+conv D=16 +aug", "416K", "76.0%"),
248
+ ]
249
+ for n, p, a in prev:
250
+ print(f" {n:<50s} {p:>8s} {a:>8s}")
251
+
252
+ print(f" {'-' * 68}")
253
+ for name, (acc, params, elapsed) in results.items():
254
+ print(f" {'ConduitBattery + ' + name:<50s} {params:>8,} {acc:>7.1%}")
255
+
256
+ best = max(results.items(), key=lambda x: x[1][0])
257
+ print(f"\n Best: ConduitBattery + {best[0]} β†’ {best[1][0]:.1%}")