aijadugar commited on
Commit
2dabf64
·
verified ·
1 Parent(s): e68972b

Implemented Vision Transformer architechture from scratch!

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ inference_samples.png filter=lfs diff=lfs merge=lfs -text
37
+ training_curves.png filter=lfs diff=lfs merge=lfs -text
.virtual_documents/__notebook_source__.ipynb ADDED
@@ -0,0 +1,854 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+
4
+ # 1. Install dependencies (uncomment if running in a fresh environment)
5
+ # !pip install -q torch torchvision datasets matplotlib scikit-learn tqdm
6
+
7
+
8
+
9
+ # 2. Imports
10
+ import os
11
+ import json
12
+ import time
13
+ import math
14
+ import random
15
+ import copy
16
+ from dataclasses import dataclass, field, asdict
17
+
18
+ import numpy as np
19
+ import torch
20
+ import torch.nn as nn
21
+ import torch.nn.functional as F
22
+ from torch.utils.data import Dataset, DataLoader
23
+
24
+ import torchvision.transforms as T
25
+ import torchvision.transforms.functional as TF
26
+
27
+ import matplotlib.pyplot as plt
28
+ from tqdm.auto import tqdm
29
+
30
+ from safetensors.torch import save_file, load_file
31
+
32
+ from PIL import Image
33
+
34
+ print("Torch:", torch.__version__)
35
+ print("CUDA available:", torch.cuda.is_available())
36
+
37
+
38
+
39
+ # 3. Reproducibility
40
+ SEED = 42
41
+ random.seed(SEED)
42
+ np.random.seed(SEED)
43
+ torch.manual_seed(SEED)
44
+ torch.cuda.manual_seed_all(SEED)
45
+
46
+
47
+
48
+ # 4. Configuration
49
+ @dataclass
50
+ class Config:
51
+ # Data
52
+ image_size: int = 224
53
+ patch_size: int = 16
54
+ num_classes: int = 151 # 150 ADE20K classes + 1 background/ignore(0)
55
+ ignore_index: int = 0 # ADE20K label 0 = unlabeled/background
56
+
57
+ # Model
58
+ embed_dim: int = 384 # ViT-Small-ish for a runnable demo (use 768 for ViT-Base)
59
+ depth: int = 8
60
+ num_heads: int = 6
61
+ mlp_ratio: float = 4.0
62
+ dropout: float = 0.1
63
+
64
+ # Training
65
+ batch_size: int = 8
66
+ epochs: int = 10 # increase for real training (e.g. 64-160)
67
+ lr: float = 3e-4
68
+ weight_decay: float = 0.05
69
+ warmup_epochs: int = 1
70
+ num_workers: int = 2
71
+ subset_size: int = None # e.g. 500 to train fast on a subset; None = full split
72
+
73
+ device: str = "cuda" if torch.cuda.is_available() else "cpu"
74
+
75
+ cfg = Config()
76
+ print(cfg)
77
+
78
+
79
+
80
+
81
+
82
+
83
+ from datasets import load_dataset
84
+
85
+ ade20k = load_dataset(
86
+ "parquet",
87
+ data_files={
88
+ "train": "https://huggingface.co/datasets/zhoubolei/scene_parse_150/resolve/refs%2Fconvert%2Fparquet/scene_parsing/train/0000.parquet",
89
+ "validation": "https://huggingface.co/datasets/zhoubolei/scene_parse_150/resolve/refs%2Fconvert%2Fparquet/scene_parsing/validation/0000.parquet",
90
+ },
91
+ features=None,
92
+ )
93
+
94
+ print(ade20k)
95
+ print(ade20k["train"].column_names)
96
+
97
+
98
+ # 6. Visualize a raw sample (image + segmentation mask)
99
+ sample = ade20k["train"][0]
100
+ fig, axes = plt.subplots(1, 2, figsize=(10, 5))
101
+ axes[0].imshow(sample["image"])
102
+ axes[0].set_title("Image")
103
+ axes[0].axis("off")
104
+
105
+ axes[1].imshow(sample["annotation"], cmap="tab20")
106
+ axes[1].set_title("Annotation (class ids)")
107
+ axes[1].axis("off")
108
+ plt.tight_layout()
109
+ plt.show()
110
+
111
+
112
+
113
+ # 7. Dataset wrapper: resize image + mask, normalize image, return tensors
114
+ IMAGENET_MEAN = [0.485, 0.456, 0.406]
115
+ IMAGENET_STD = [0.229, 0.224, 0.225]
116
+
117
+ class ADE20KSegDataset(Dataset):
118
+ # Wraps a HF `scene_parse_150` split into (image_tensor, mask_tensor) pairs.
119
+ def __init__(self, hf_split, image_size=224, train=True, subset_size=None):
120
+ self.data = hf_split
121
+ self.image_size = image_size
122
+ self.train = train
123
+ self.indices = list(range(len(self.data)))
124
+ if subset_size is not None:
125
+ self.indices = self.indices[:subset_size]
126
+
127
+ self.img_tf = T.Compose([
128
+ T.Resize((image_size, image_size), interpolation=T.InterpolationMode.BILINEAR),
129
+ T.ToTensor(),
130
+ T.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD),
131
+ ])
132
+
133
+ def __len__(self):
134
+ return len(self.indices)
135
+
136
+ def __getitem__(self, idx):
137
+ item = self.data[self.indices[idx]]
138
+ image = item["image"].convert("RGB")
139
+ mask = item["annotation"] # PIL "L" image, pixel values = class id (0..150)
140
+
141
+ # Simple augmentation: random horizontal flip (train only)
142
+ if self.train and random.random() < 0.5:
143
+ image = TF.hflip(image)
144
+ mask = TF.hflip(mask)
145
+
146
+ img_t = self.img_tf(image)
147
+
148
+ mask = mask.resize((self.image_size, self.image_size), Image.NEAREST)
149
+ mask_t = torch.from_numpy(np.array(mask)).long()
150
+
151
+ return img_t, mask_t
152
+
153
+
154
+ train_dataset = ADE20KSegDataset(ade20k["train"], image_size=cfg.image_size,
155
+ train=True, subset_size=cfg.subset_size)
156
+ val_dataset = ADE20KSegDataset(ade20k["validation"], image_size=cfg.image_size,
157
+ train=False, subset_size=cfg.subset_size)
158
+
159
+ print("Train size:", len(train_dataset), "| Val size:", len(val_dataset))
160
+
161
+
162
+
163
+ # 8. DataLoaders
164
+ train_loader = DataLoader(
165
+ train_dataset, batch_size=cfg.batch_size, shuffle=True,
166
+ num_workers=cfg.num_workers, pin_memory=True, drop_last=True, persistent_workers=True,
167
+ )
168
+ val_loader = DataLoader(
169
+ val_dataset, batch_size=cfg.batch_size, shuffle=False,
170
+ num_workers=cfg.num_workers, pin_memory=True, persistent_workers=True,
171
+ )
172
+
173
+ imgs, masks = next(iter(train_loader))
174
+ print("Batch image shape:", imgs.shape, "| Batch mask shape:", masks.shape)
175
+
176
+
177
+
178
+
179
+
180
+
181
+ # 9. Patch Embedding
182
+ class PatchEmbedding(nn.Module):
183
+ def __init__(self, image_size=224, patch_size=16, in_channels=3, embed_dim=768):
184
+ super().__init__()
185
+ assert image_size % patch_size == 0
186
+
187
+ self.grid_size = image_size // patch_size
188
+ self.num_patches = self.grid_size ** 2
189
+
190
+ self.projection = nn.Conv2d(
191
+ in_channels, embed_dim, kernel_size=patch_size, stride=patch_size
192
+ )
193
+
194
+ def forward(self, x):
195
+ # x: [B, C, H, W]
196
+ x = self.projection(x) # [B, D, H/P, W/P]
197
+ x = x.flatten(2) # [B, D, N]
198
+ x = x.transpose(1, 2) # [B, N, D]
199
+ return x
200
+
201
+
202
+
203
+ # 10. Multi-Head Self-Attention
204
+ class MultiHeadSelfAttention(nn.Module):
205
+ def __init__(self, embed_dim, num_heads, dropout=0.0):
206
+ super().__init__()
207
+ assert embed_dim % num_heads == 0
208
+
209
+ self.num_heads = num_heads
210
+ self.head_dim = embed_dim // num_heads
211
+
212
+ self.qkv = nn.Linear(embed_dim, 3 * embed_dim)
213
+ self.proj = nn.Linear(embed_dim, embed_dim)
214
+ self.dropout = nn.Dropout(dropout)
215
+
216
+ def forward(self, x):
217
+ B, N, D = x.shape
218
+
219
+ qkv = self.qkv(x)
220
+ qkv = qkv.reshape(B, N, 3, self.num_heads, self.head_dim)
221
+ qkv = qkv.permute(2, 0, 3, 1, 4)
222
+ Q, K, V = qkv[0], qkv[1], qkv[2]
223
+
224
+ attention = (Q @ K.transpose(-2, -1)) / (self.head_dim ** 0.5)
225
+ attention = attention.softmax(dim=-1)
226
+ attention = self.dropout(attention)
227
+
228
+ x = attention @ V
229
+ x = x.transpose(1, 2)
230
+ x = x.reshape(B, N, D)
231
+ x = self.proj(x)
232
+ return x
233
+
234
+
235
+
236
+ # 11. MLP block
237
+ class MLP(nn.Module):
238
+ def __init__(self, embed_dim, mlp_ratio=4.0, dropout=0.0):
239
+ super().__init__()
240
+ hidden_dim = int(embed_dim * mlp_ratio)
241
+
242
+ self.fc1 = nn.Linear(embed_dim, hidden_dim)
243
+ self.act = nn.GELU()
244
+ self.dropout1 = nn.Dropout(dropout)
245
+
246
+ self.fc2 = nn.Linear(hidden_dim, embed_dim)
247
+ self.dropout2 = nn.Dropout(dropout)
248
+
249
+ def forward(self, x):
250
+ x = self.fc1(x)
251
+ x = self.act(x)
252
+ x = self.dropout1(x)
253
+ x = self.fc2(x)
254
+ x = self.dropout2(x)
255
+ return x
256
+
257
+
258
+
259
+ # 12. Transformer Encoder Block (pre-norm, residual)
260
+ class TransformerEncoderBlock(nn.Module):
261
+ def __init__(self, embed_dim, num_heads, mlp_ratio=4.0, dropout=0.0):
262
+ super().__init__()
263
+ self.norm1 = nn.LayerNorm(embed_dim)
264
+ self.attention = MultiHeadSelfAttention(embed_dim, num_heads, dropout)
265
+
266
+ self.norm2 = nn.LayerNorm(embed_dim)
267
+ self.mlp = MLP(embed_dim, mlp_ratio, dropout)
268
+
269
+ def forward(self, x):
270
+ x = x + self.attention(self.norm1(x))
271
+ x = x + self.mlp(self.norm2(x))
272
+ return x
273
+
274
+
275
+
276
+
277
+
278
+
279
+ # 13. ViT backbone (no classification head — returns patch-token features)
280
+ class ViTBackbone(nn.Module):
281
+ def __init__(
282
+ self,
283
+ image_size=224,
284
+ patch_size=16,
285
+ in_channels=3,
286
+ embed_dim=768,
287
+ depth=12,
288
+ num_heads=12,
289
+ mlp_ratio=4.0,
290
+ dropout=0.0,
291
+ use_cls_token=True,
292
+ ):
293
+ super().__init__()
294
+ self.patch_embedding = PatchEmbedding(image_size, patch_size, in_channels, embed_dim)
295
+ self.grid_size = self.patch_embedding.grid_size
296
+ num_patches = self.patch_embedding.num_patches
297
+ self.use_cls_token = use_cls_token
298
+
299
+ self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim)) if use_cls_token else None
300
+ num_tokens = num_patches + (1 if use_cls_token else 0)
301
+ self.pos_embedding = nn.Parameter(torch.zeros(1, num_tokens, embed_dim))
302
+
303
+ self.dropout = nn.Dropout(dropout)
304
+
305
+ self.blocks = nn.ModuleList([
306
+ TransformerEncoderBlock(embed_dim, num_heads, mlp_ratio, dropout)
307
+ for _ in range(depth)
308
+ ])
309
+ self.norm = nn.LayerNorm(embed_dim)
310
+
311
+ self._init_weights()
312
+
313
+ def _init_weights(self):
314
+ if self.cls_token is not None:
315
+ nn.init.trunc_normal_(self.cls_token, std=0.02)
316
+ nn.init.trunc_normal_(self.pos_embedding, std=0.02)
317
+
318
+ def forward(self, x):
319
+ x = self.patch_embedding(x) # [B, N, D]
320
+ B = x.shape[0]
321
+
322
+ if self.use_cls_token:
323
+ cls_token = self.cls_token.expand(B, -1, -1)
324
+ x = torch.cat([cls_token, x], dim=1)
325
+
326
+ x = x + self.pos_embedding
327
+ x = self.dropout(x)
328
+
329
+ for block in self.blocks:
330
+ x = block(x)
331
+
332
+ x = self.norm(x)
333
+
334
+ if self.use_cls_token:
335
+ patch_tokens = x[:, 1:] # drop CLS, keep patch tokens
336
+ else:
337
+ patch_tokens = x
338
+ return patch_tokens # [B, N, D]
339
+
340
+
341
+
342
+ # 14. Segmentation decoder head (Segmenter-style linear decoder)
343
+ class LinearSegmentationHead(nn.Module):
344
+ def __init__(self, embed_dim, num_classes, grid_size, image_size):
345
+ super().__init__()
346
+ self.grid_size = grid_size
347
+ self.image_size = image_size
348
+ self.classifier = nn.Conv2d(embed_dim, num_classes, kernel_size=1)
349
+
350
+ def forward(self, patch_tokens):
351
+ # patch_tokens: [B, N, D] -> [B, D, gh, gw]
352
+ B, N, D = patch_tokens.shape
353
+ gh = gw = self.grid_size
354
+ x = patch_tokens.transpose(1, 2).reshape(B, D, gh, gw)
355
+
356
+ logits = self.classifier(x) # [B, num_classes, gh, gw]
357
+ logits = F.interpolate(
358
+ logits, size=(self.image_size, self.image_size),
359
+ mode="bilinear", align_corners=False,
360
+ )
361
+ return logits # [B, num_classes, H, W]
362
+
363
+
364
+ class ViTSegmentation(nn.Module):
365
+ def __init__(self, cfg: Config):
366
+ super().__init__()
367
+ self.backbone = ViTBackbone(
368
+ image_size=cfg.image_size,
369
+ patch_size=cfg.patch_size,
370
+ embed_dim=cfg.embed_dim,
371
+ depth=cfg.depth,
372
+ num_heads=cfg.num_heads,
373
+ mlp_ratio=cfg.mlp_ratio,
374
+ dropout=cfg.dropout,
375
+ use_cls_token=True,
376
+ )
377
+ self.head = LinearSegmentationHead(
378
+ embed_dim=cfg.embed_dim,
379
+ num_classes=cfg.num_classes,
380
+ grid_size=self.backbone.grid_size,
381
+ image_size=cfg.image_size,
382
+ )
383
+
384
+ def forward(self, x):
385
+ tokens = self.backbone(x)
386
+ logits = self.head(tokens)
387
+ return logits
388
+
389
+
390
+
391
+ # 15. Instantiate and sanity-check the model
392
+
393
+ model = ViTSegmentation(cfg)
394
+
395
+ # Use both Tesla T4 GPUs
396
+ if torch.cuda.device_count() > 1:
397
+ print(f"Using {torch.cuda.device_count()} GPUs")
398
+ model = torch.nn.DataParallel(model)
399
+
400
+ model = model.to(cfg.device)
401
+
402
+ n_params = sum(
403
+ p.numel() for p in model.parameters()
404
+ if p.requires_grad
405
+ )
406
+
407
+ print(f"Trainable parameters: {n_params / 1e6:.2f}M")
408
+
409
+ with torch.no_grad():
410
+ dummy = torch.randn(
411
+ 2,
412
+ 3,
413
+ cfg.image_size,
414
+ cfg.image_size,
415
+ device=cfg.device
416
+ )
417
+
418
+ out = model(dummy)
419
+
420
+ print("Output shape:", out.shape)
421
+ # [2, num_classes, image_size, image_size]
422
+
423
+
424
+
425
+
426
+
427
+ # 16. Loss
428
+ criterion = nn.CrossEntropyLoss(ignore_index=cfg.ignore_index)
429
+
430
+
431
+
432
+ # 17. Segmentation metrics: pixel accuracy, mean class accuracy, mIoU
433
+ class SegmentationMetrics:
434
+ def __init__(self, num_classes, ignore_index=0):
435
+ self.num_classes = num_classes
436
+ self.ignore_index = ignore_index
437
+ self.confusion = np.zeros((num_classes, num_classes), dtype=np.int64)
438
+
439
+ def update(self, preds, targets):
440
+ preds = preds.detach().cpu().numpy().reshape(-1)
441
+ targets = targets.detach().cpu().numpy().reshape(-1)
442
+ mask = targets != self.ignore_index
443
+ preds, targets = preds[mask], targets[mask]
444
+ idx = targets * self.num_classes + preds
445
+ cm = np.bincount(idx, minlength=self.num_classes ** 2)
446
+ self.confusion += cm.reshape(self.num_classes, self.num_classes)
447
+
448
+ def compute(self):
449
+ cm = self.confusion.astype(np.float64)
450
+ intersection = np.diag(cm)
451
+ union = cm.sum(1) + cm.sum(0) - intersection
452
+ iou = intersection / np.maximum(union, 1)
453
+ valid = union > 0
454
+
455
+ pixel_acc = intersection.sum() / max(cm.sum(), 1)
456
+ mean_acc = np.mean(
457
+ (intersection[valid] / np.maximum(cm.sum(1)[valid], 1))
458
+ ) if valid.any() else 0.0
459
+ miou = np.mean(iou[valid]) if valid.any() else 0.0
460
+
461
+ return {
462
+ "pixel_accuracy": float(pixel_acc),
463
+ "mean_class_accuracy": float(mean_acc),
464
+ "mIoU": float(miou),
465
+ "per_class_iou": iou,
466
+ }
467
+
468
+ def reset(self):
469
+ self.confusion[:] = 0
470
+
471
+
472
+
473
+ # 18. Optimizer + cosine LR schedule with linear warmup
474
+ optimizer = torch.optim.AdamW(model.parameters(), lr=cfg.lr, weight_decay=cfg.weight_decay)
475
+
476
+ steps_per_epoch = max(len(train_loader), 1)
477
+ total_steps = steps_per_epoch * cfg.epochs
478
+ warmup_steps = steps_per_epoch * cfg.warmup_epochs
479
+
480
+ def lr_lambda(step):
481
+ if step < warmup_steps:
482
+ return step / max(1, warmup_steps)
483
+ progress = (step - warmup_steps) / max(1, total_steps - warmup_steps)
484
+ return 0.5 * (1 + math.cos(math.pi * progress))
485
+
486
+ scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)
487
+
488
+
489
+
490
+
491
+
492
+
493
+ # 19. train / eval epoch functions
494
+ def train_one_epoch(model, loader, optimizer, scheduler, criterion, device):
495
+ model.train()
496
+ running_loss = 0.0
497
+ lrs = []
498
+ for imgs, masks in tqdm(loader, desc="train", leave=False):
499
+ imgs, masks = imgs.to(device), masks.to(device)
500
+
501
+ optimizer.zero_grad()
502
+ logits = model(imgs)
503
+ loss = criterion(logits, masks)
504
+ loss.backward()
505
+ optimizer.step()
506
+ scheduler.step()
507
+
508
+ running_loss += loss.item() * imgs.size(0)
509
+ lrs.append(scheduler.get_last_lr()[0])
510
+
511
+ return running_loss / len(loader.dataset), lrs
512
+
513
+
514
+ @torch.no_grad()
515
+ def evaluate(model, loader, criterion, device, num_classes, ignore_index):
516
+ model.eval()
517
+ running_loss = 0.0
518
+ metrics = SegmentationMetrics(num_classes, ignore_index)
519
+
520
+ for imgs, masks in tqdm(loader, desc="val", leave=False):
521
+ imgs, masks = imgs.to(device), masks.to(device)
522
+ logits = model(imgs)
523
+ loss = criterion(logits, masks)
524
+ running_loss += loss.item() * imgs.size(0)
525
+
526
+ preds = logits.argmax(dim=1)
527
+ metrics.update(preds, masks)
528
+
529
+ val_loss = running_loss / len(loader.dataset)
530
+ results = metrics.compute()
531
+ results["loss"] = val_loss
532
+ return results
533
+
534
+
535
+
536
+ # 20. Run training
537
+ history = {
538
+ "train_loss": [],
539
+ "val_loss": [],
540
+ "val_pixel_acc": [],
541
+ "val_mean_acc": [],
542
+ "val_miou": [],
543
+ "lr": [],
544
+ }
545
+
546
+ best_miou = -1.0
547
+ best_state = None
548
+
549
+ for epoch in range(1, cfg.epochs + 1):
550
+ t0 = time.time()
551
+
552
+ train_loss, lrs = train_one_epoch(model, train_loader, optimizer, scheduler, criterion, cfg.device)
553
+ val_results = evaluate(model, val_loader, criterion, cfg.device, cfg.num_classes, cfg.ignore_index)
554
+
555
+ history["train_loss"].append(train_loss)
556
+ history["val_loss"].append(val_results["loss"])
557
+ history["val_pixel_acc"].append(val_results["pixel_accuracy"])
558
+ history["val_mean_acc"].append(val_results["mean_class_accuracy"])
559
+ history["val_miou"].append(val_results["mIoU"])
560
+ history["lr"].extend(lrs)
561
+
562
+ if val_results["mIoU"] > best_miou:
563
+ best_miou = val_results["mIoU"]
564
+ best_state = copy.deepcopy(model.state_dict())
565
+
566
+ dt = time.time() - t0
567
+ print(
568
+ f"Epoch {epoch}/{cfg.epochs} | "
569
+ f"train_loss={train_loss:.4f} | val_loss={val_results['loss']:.4f} | "
570
+ f"val_mIoU={val_results['mIoU']:.4f} | pixel_acc={val_results['pixel_accuracy']:.4f} | "
571
+ f"{dt:.1f}s"
572
+ )
573
+
574
+ print(f"Best val mIoU: {best_miou:.4f}")
575
+
576
+
577
+
578
+ os.makedirs("checkpoints", exist_ok=True)
579
+
580
+ if best_state is not None:
581
+ save_file(
582
+ best_state,
583
+ "checkpoints/vit_seg_best.safetensors"
584
+ )
585
+
586
+ print("Saved checkpoints/vit_seg_best.safetensors")
587
+
588
+
589
+
590
+
591
+
592
+ # 22. Plot train/val loss, mIoU, pixel accuracy, and the LR schedule
593
+ fig, axes = plt.subplots(2, 2, figsize=(13, 9))
594
+
595
+ epochs_range = range(1, len(history["train_loss"]) + 1)
596
+
597
+ axes[0, 0].plot(epochs_range, history["train_loss"], marker="o", label="train loss")
598
+ axes[0, 0].plot(epochs_range, history["val_loss"], marker="o", label="val loss")
599
+ axes[0, 0].set_title("Loss")
600
+ axes[0, 0].set_xlabel("epoch")
601
+ axes[0, 0].set_ylabel("cross-entropy loss")
602
+ axes[0, 0].legend()
603
+ axes[0, 0].grid(alpha=0.3)
604
+
605
+ axes[0, 1].plot(epochs_range, history["val_miou"], marker="o", color="green")
606
+ axes[0, 1].set_title("Validation mIoU")
607
+ axes[0, 1].set_xlabel("epoch")
608
+ axes[0, 1].set_ylabel("mIoU")
609
+ axes[0, 1].grid(alpha=0.3)
610
+
611
+ axes[1, 0].plot(epochs_range, history["val_pixel_acc"], marker="o", label="pixel accuracy")
612
+ axes[1, 0].plot(epochs_range, history["val_mean_acc"], marker="o", label="mean class accuracy")
613
+ axes[1, 0].set_title("Validation Accuracy")
614
+ axes[1, 0].set_xlabel("epoch")
615
+ axes[1, 0].legend()
616
+ axes[1, 0].grid(alpha=0.3)
617
+
618
+ axes[1, 1].plot(history["lr"], color="purple")
619
+ axes[1, 1].set_title("Learning Rate Schedule (per step)")
620
+ axes[1, 1].set_xlabel("step")
621
+ axes[1, 1].set_ylabel("lr")
622
+ axes[1, 1].grid(alpha=0.3)
623
+
624
+ plt.tight_layout()
625
+ plt.savefig("training_curves.png", dpi=150)
626
+ plt.show()
627
+
628
+
629
+
630
+
631
+
632
+
633
+ # 23. ADE20K color palette (151 colors incl. background) for visualization
634
+ def ade20k_palette(num_classes=151, seed=1):
635
+ rng = np.random.RandomState(seed)
636
+ palette = rng.randint(0, 255, size=(num_classes, 3), dtype=np.uint8)
637
+ palette[0] = np.array([0, 0, 0], dtype=np.uint8) # background = black
638
+ return palette
639
+
640
+ PALETTE = ade20k_palette(cfg.num_classes)
641
+
642
+ def colorize_mask(mask, palette=PALETTE):
643
+ mask = mask.astype(np.int64)
644
+ return palette[mask]
645
+
646
+
647
+
648
+ ckpt_path = "checkpoints/vit_seg_best.safetensors"
649
+
650
+ if os.path.exists(ckpt_path):
651
+ state_dict = load_file(
652
+ ckpt_path,
653
+ device=str(cfg.device)
654
+ )
655
+ model.load_state_dict(state_dict)
656
+ print("Loaded best checkpoint.")
657
+
658
+ model.eval()
659
+
660
+
661
+ # 25. Run inference on a handful of validation images and visualize
662
+ @torch.no_grad()
663
+ def run_inference(model, dataset, indices, device):
664
+ imgs, gts, preds = [], [], []
665
+ for i in indices:
666
+ img_t, mask_t = dataset[i]
667
+ logits = model(img_t.unsqueeze(0).to(device))
668
+ pred = logits.argmax(dim=1).squeeze(0).cpu().numpy()
669
+
670
+ # de-normalize image for display
671
+ img = img_t.clone()
672
+ for c, (m, s) in enumerate(zip(IMAGENET_MEAN, IMAGENET_STD)):
673
+ img[c] = img[c] * s + m
674
+ img = img.permute(1, 2, 0).clamp(0, 1).numpy()
675
+
676
+ imgs.append(img)
677
+ gts.append(mask_t.numpy())
678
+ preds.append(pred)
679
+ return imgs, gts, preds
680
+
681
+
682
+ sample_indices = random.sample(range(len(val_dataset)), k=min(4, len(val_dataset)))
683
+ imgs, gts, preds = run_inference(model, val_dataset, sample_indices, cfg.device)
684
+
685
+ fig, axes = plt.subplots(len(sample_indices), 3, figsize=(11, 3.5 * len(sample_indices)))
686
+ if len(sample_indices) == 1:
687
+ axes = axes[None, :]
688
+
689
+ for row, (img, gt, pred) in enumerate(zip(imgs, gts, preds)):
690
+ axes[row, 0].imshow(img)
691
+ axes[row, 0].set_title("Image")
692
+ axes[row, 0].axis("off")
693
+
694
+ axes[row, 1].imshow(colorize_mask(gt))
695
+ axes[row, 1].set_title("Ground Truth")
696
+ axes[row, 1].axis("off")
697
+
698
+ axes[row, 2].imshow(colorize_mask(pred))
699
+ axes[row, 2].set_title("Prediction")
700
+ axes[row, 2].axis("off")
701
+
702
+ plt.tight_layout()
703
+ plt.savefig("inference_samples.png", dpi=150)
704
+ plt.show()
705
+
706
+
707
+
708
+ # Config — export as JSON
709
+ import json
710
+
711
+ config_dict = {
712
+ "data": {
713
+ "dataset": "scene_parse_150 (ADE20K, 150 classes)",
714
+ "image_size": 224,
715
+ "patch_size": 16,
716
+ "num_classes": 151,
717
+ "ignore_index": 0
718
+ },
719
+ "model": {
720
+ "architecture": "ViT-Segmentation (first-principles backbone + linear decoder head)",
721
+ "embed_dim": 384,
722
+ "depth": 8,
723
+ "num_heads": 6,
724
+ "mlp_ratio": 4.0,
725
+ "dropout": 0.1
726
+ },
727
+ "training": {
728
+ "batch_size": 8,
729
+ "epochs": 5,
730
+ "lr": 3e-4,
731
+ "weight_decay": 0.05,
732
+ "warmup_epochs": 1,
733
+ "num_workers": 2,
734
+ "subset_size": None,
735
+ "device": "cuda if available else cpu"
736
+ },
737
+ "evolution_search": {
738
+ "population_size": 6,
739
+ "generations": 4,
740
+ "elite_k": 2,
741
+ "steps_per_candidate": 30,
742
+ "subset_size": 64,
743
+ "batch_size": 4
744
+ }
745
+ }
746
+
747
+ with open("config.json", "w") as f:
748
+ json.dump(config_dict, f, indent=2)
749
+
750
+
751
+ # README.md — generated for this notebook, includes result images
752
+ readme_content = """
753
+ # ViT Semantic Segmentation on ADE20K
754
+
755
+ A Vision Transformer built from first principles (patch embedding, multi-head
756
+ self-attention, MLP, transformer encoder blocks) adapted for semantic
757
+ segmentation on the ADE20K dataset (150 classes), with training, evaluation,
758
+ benchmarking against published ViT segmentation models, and an evolutionary
759
+ hyperparameter search.
760
+
761
+ ## Contents
762
+
763
+ - `vit-ade20k-segmentation.ipynb` — the full notebook
764
+ - `config.json` — model/training/search configuration
765
+ - `checkpoints/vit_seg_best.pt` — best model checkpoint (created after training)
766
+
767
+ ## Configuration
768
+
769
+ See [`config.json`](config.json) for the exact data, model, training, and
770
+ evolutionary-search settings used to produce the results below.
771
+
772
+ ## Results
773
+
774
+ ### Training curves
775
+
776
+ get_ipython().getoutput("[Training curves](training_curves.png)")
777
+
778
+ Train/val loss, validation mIoU, validation pixel & mean-class accuracy, and
779
+ the learning-rate schedule.
780
+
781
+ ### Inference samples
782
+
783
+ get_ipython().getoutput("[Inference samples](inference_samples.png)")
784
+
785
+ Image / ground truth / prediction, side by side, for a few validation images.
786
+
787
+ ### Benchmark comparison
788
+
789
+ get_ipython().getoutput("[Benchmark comparison](benchmark_comparison.png)")
790
+
791
+ mIoU compared against published ViT-based segmentation models (SETR,
792
+ Segmenter, SegFormer, DPT) on ADE20K val, plus an accuracy-vs-parameters
793
+ scatter.
794
+
795
+ ### Evolutionary search
796
+
797
+ get_ipython().getoutput("[Evolution fitness](evolution_fitness.png)")
798
+
799
+ Best and mean fitness (proxy mIoU) per generation from the evolutionary
800
+ hyperparameter search.
801
+
802
+ ## How to reproduce
803
+
804
+ 1. Install dependencies (see the first cell of the notebook).
805
+ 2. Run all cells top to bottom.
806
+ 3. Images (`training_curves.png`, `inference_samples.png`,
807
+ `benchmark_comparison.png`, `evolution_fitness.png`) and `config.json`
808
+ are written to the working directory as you go.
809
+
810
+ ## Notes
811
+
812
+ - Published benchmark numbers are commonly-cited approximations from the
813
+ original papers — verify exact figures before citing them elsewhere.
814
+ - Demo defaults (small `embed_dim`/`depth`, few epochs) are set to run
815
+ quickly; scale up for real training.
816
+ """
817
+
818
+ with open("README.md", "w") as f:
819
+ f.write(readme_content)
820
+
821
+
822
+ get_ipython().run_line_magic("ls", "")
823
+
824
+
825
+ import shutil
826
+
827
+ os.makedirs("/kaggle/working/vit", exist_ok=True)
828
+
829
+ for item in os.listdir("/kaggle/working"):
830
+ if item != "vit":
831
+ shutil.move(
832
+ os.path.join("/kaggle/working", item),
833
+ "/kaggle/working/vit"
834
+ )
835
+
836
+
837
+ from huggingface_hub import HfApi
838
+
839
+ api = HfApi(token="hf_xxxx")
840
+
841
+ api.create_repo(
842
+ repo_id="aijadugar/ViT-ADE20K-Segmentation",
843
+ exist_ok=True,
844
+ )
845
+
846
+ api.upload_folder(
847
+ repo_id="aijadugar/ViT-ADE20K-Segmentation",
848
+ folder_path="/kaggle/working/vit",
849
+ commit_message="Implemented Vision Transformer architechture from scratch!",
850
+ repo_type="model",
851
+ )
852
+
853
+
854
+
README.md ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # ViT Semantic Segmentation on ADE20K
3
+
4
+ A Vision Transformer built from first principles (patch embedding, multi-head
5
+ self-attention, MLP, transformer encoder blocks) adapted for semantic
6
+ segmentation on the ADE20K dataset (150 classes), with training, evaluation,
7
+ benchmarking against published ViT segmentation models, and an evolutionary
8
+ hyperparameter search.
9
+
10
+ ## Contents
11
+
12
+ - `vit-ade20k-segmentation.ipynb` — the full notebook
13
+ - `config.json` — model/training/search configuration
14
+ - `checkpoints/vit_seg_best.pt` — best model checkpoint (created after training)
15
+
16
+ ## Configuration
17
+
18
+ See [`config.json`](config.json) for the exact data, model, training, and
19
+ evolutionary-search settings used to produce the results below.
20
+
21
+ ## Results
22
+
23
+ ### Training curves
24
+
25
+ ![Training curves](training_curves.png)
26
+
27
+ Train/val loss, validation mIoU, validation pixel & mean-class accuracy, and
28
+ the learning-rate schedule.
29
+
30
+ ### Inference samples
31
+
32
+ ![Inference samples](inference_samples.png)
33
+
34
+ Image / ground truth / prediction, side by side, for a few validation images.
35
+
36
+ ### Benchmark comparison
37
+
38
+ ![Benchmark comparison](benchmark_comparison.png)
39
+
40
+ mIoU compared against published ViT-based segmentation models (SETR,
41
+ Segmenter, SegFormer, DPT) on ADE20K val, plus an accuracy-vs-parameters
42
+ scatter.
43
+
44
+ ### Evolutionary search
45
+
46
+ ![Evolution fitness](evolution_fitness.png)
47
+
48
+ Best and mean fitness (proxy mIoU) per generation from the evolutionary
49
+ hyperparameter search.
50
+
51
+ ## How to reproduce
52
+
53
+ 1. Install dependencies (see the first cell of the notebook).
54
+ 2. Run all cells top to bottom.
55
+ 3. Images (`training_curves.png`, `inference_samples.png`,
56
+ `benchmark_comparison.png`, `evolution_fitness.png`) and `config.json`
57
+ are written to the working directory as you go.
58
+
59
+ ## Notes
60
+
61
+ - Published benchmark numbers are commonly-cited approximations from the
62
+ original papers — verify exact figures before citing them elsewhere.
63
+ - Demo defaults (small `embed_dim`/`depth`, few epochs) are set to run
64
+ quickly; scale up for real training.
checkpoints/vit_seg_best.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:03051ca030569c0f53f561b3ab874eb7326bde8290ee44ceb6d4ce55faeecbde
3
+ size 58515052
config.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "data": {
3
+ "dataset": "scene_parse_150 (ADE20K, 150 classes)",
4
+ "image_size": 224,
5
+ "patch_size": 16,
6
+ "num_classes": 151,
7
+ "ignore_index": 0
8
+ },
9
+ "model": {
10
+ "architecture": "ViT-Segmentation (first-principles backbone + linear decoder head)",
11
+ "embed_dim": 384,
12
+ "depth": 8,
13
+ "num_heads": 6,
14
+ "mlp_ratio": 4.0,
15
+ "dropout": 0.1
16
+ },
17
+ "training": {
18
+ "batch_size": 8,
19
+ "epochs": 5,
20
+ "lr": 0.0003,
21
+ "weight_decay": 0.05,
22
+ "warmup_epochs": 1,
23
+ "num_workers": 2,
24
+ "subset_size": null,
25
+ "device": "cuda if available else cpu"
26
+ },
27
+ "evolution_search": {
28
+ "population_size": 6,
29
+ "generations": 4,
30
+ "elite_k": 2,
31
+ "steps_per_candidate": 30,
32
+ "subset_size": 64,
33
+ "batch_size": 4
34
+ }
35
+ }
inference_samples.png ADDED

Git LFS Details

  • SHA256: 45ec18a4f5af58e2f443ee20be5cc1ae72aaf5d5743b16367c18bfa4243806fc
  • Pointer size: 132 Bytes
  • Size of remote file: 1.1 MB
training_curves.png ADDED

Git LFS Details

  • SHA256: 47da96a8b98e5828f4de9d4f081431e90bdff0a79eb1724156c964c70ba5362d
  • Pointer size: 131 Bytes
  • Size of remote file: 180 kB