yusiwen commited on
Commit
e8731e3
·
unverified ·
1 Parent(s): 5df65af

feat: add CNN vs ViT benchmark script (untrained)

Browse files
Files changed (2) hide show
  1. ROADMAP.md +1 -1
  2. scripts/benchmark_cnn_vit.py +180 -0
ROADMAP.md CHANGED
@@ -32,5 +32,5 @@
32
 
33
  - Train & validate on NVIDIA GPU
34
  - ~~Demo scripts for CV models (`vit/demo.py`, `unet/demo.py`, `yolo/demo.py`)~~ ✅ done
35
- - CNN vs ViT benchmark on CIFAR-10
36
  - Multi-GPU / WandB / hyperparameter search
 
32
 
33
  - Train & validate on NVIDIA GPU
34
  - ~~Demo scripts for CV models (`vit/demo.py`, `unet/demo.py`, `yolo/demo.py`)~~ ✅ done
35
+ - ~~CNN vs ViT benchmark on CIFAR-10~~ ✅ script done, training not executed (run `uv run python scripts/benchmark_cnn_vit.py`)`
36
  - Multi-GPU / WandB / hyperparameter search
scripts/benchmark_cnn_vit.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CNN vs ViT Benchmark on CIFAR-10.
3
+
4
+ Trains both models with the same settings (epochs, batch_size, lr, scheduler),
5
+ records loss/accuracy/timing per epoch, and outputs a comparison table + plot.
6
+
7
+ Usage:
8
+ uv run python scripts/benchmark_cnn_vit.py
9
+
10
+ Requires: matplotlib (for plot), pytorch (for training)
11
+ """
12
+
13
+ import time
14
+ import torch
15
+ import torch.nn as nn
16
+ import torch.optim as optim
17
+ from torch.optim.lr_scheduler import CosineAnnealingLR
18
+ from torch.utils.data import DataLoader
19
+ from torchvision import transforms
20
+ from datasets import load_dataset
21
+
22
+ from cv.simplecnn.model import SimpleCNN
23
+ from cv.vit.model import ViT
24
+
25
+
26
+ CIFAR10_MEAN = (0.4914, 0.4822, 0.4465)
27
+ CIFAR10_STD = (0.2470, 0.2435, 0.2616)
28
+ CIFAR10_CLASSES = [
29
+ "airplane", "automobile", "bird", "cat", "deer",
30
+ "dog", "frog", "horse", "ship", "truck",
31
+ ]
32
+
33
+ NUM_EPOCHS = 30
34
+ BATCH_SIZE = 128
35
+ LR = 0.001
36
+
37
+
38
+ def _build_transform(augment=False):
39
+ ops = [transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip()] if augment else []
40
+ ops.extend([transforms.ToTensor(), transforms.Normalize(CIFAR10_MEAN, CIFAR10_STD)])
41
+ return transforms.Compose(ops)
42
+
43
+
44
+ def _transform_batch(batch, fn):
45
+ batch["img"] = [fn(img.convert("RGB")) for img in batch["img"]]
46
+ return batch
47
+
48
+
49
+ def load_data(num_workers=4):
50
+ train_ds = load_dataset("uoft-cs/cifar10", split="train")
51
+ test_ds = load_dataset("uoft-cs/cifar10", split="test")
52
+ train_ds.set_transform(lambda b: _transform_batch(b, _build_transform(augment=True)))
53
+ test_ds.set_transform(lambda b: _transform_batch(b, _build_transform(augment=False)))
54
+
55
+ train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=num_workers)
56
+ test_loader = DataLoader(test_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=num_workers)
57
+ return train_loader, test_loader
58
+
59
+
60
+ def train_model(model, train_loader, test_loader, device, name="Model"):
61
+ model = model.to(device)
62
+ criterion = nn.CrossEntropyLoss()
63
+ optimizer = optim.Adam(model.parameters(), lr=LR)
64
+ scheduler = CosineAnnealingLR(optimizer, T_max=NUM_EPOCHS)
65
+
66
+ history = {"loss": [], "test_acc": [], "time_per_epoch": []}
67
+
68
+ for epoch in range(1, NUM_EPOCHS + 1):
69
+ t0 = time.time()
70
+
71
+ model.train()
72
+ train_loss = 0.0
73
+ for batch in train_loader:
74
+ images, labels = batch["img"].to(device), batch["label"].to(device)
75
+ optimizer.zero_grad()
76
+ outputs = model(images)
77
+ loss = criterion(outputs, labels)
78
+ loss.backward()
79
+ optimizer.step()
80
+ train_loss += loss.item()
81
+ scheduler.step()
82
+
83
+ model.eval()
84
+ correct = total = 0
85
+ with torch.no_grad():
86
+ for batch in test_loader:
87
+ images, labels = batch["img"].to(device), batch["label"].to(device)
88
+ outputs = model(images)
89
+ _, pred = torch.max(outputs, 1)
90
+ correct += (pred == labels).sum().item()
91
+ total += labels.size(0)
92
+
93
+ avg_loss = train_loss / len(train_loader)
94
+ test_acc = correct / total * 100
95
+ epoch_time = time.time() - t0
96
+
97
+ history["loss"].append(avg_loss)
98
+ history["test_acc"].append(test_acc)
99
+ history["time_per_epoch"].append(epoch_time)
100
+
101
+ print(f"{name:12s} Epoch [{epoch:2d}/{NUM_EPOCHS}] "
102
+ f"Loss: {avg_loss:.4f} Test Acc: {test_acc:.2f}% "
103
+ f"{epoch_time:.1f}s")
104
+
105
+ return history
106
+
107
+
108
+ def print_table(cnn_hist, vit_hist, cnn_params, vit_params):
109
+ print("\n" + "=" * 60)
110
+ print("CNN vs ViT Benchmark on CIFAR-10")
111
+ print("=" * 60)
112
+
113
+ cnn_acc = cnn_hist["test_acc"][-1]
114
+ vit_acc = vit_hist["test_acc"][-1]
115
+ cnn_time = sum(cnn_hist["time_per_epoch"])
116
+ vit_time = sum(vit_hist["time_per_epoch"])
117
+ cnn_70 = next((i + 1 for i, a in enumerate(cnn_hist["test_acc"]) if a >= 70), NUM_EPOCHS)
118
+ vit_70 = next((i + 1 for i, a in enumerate(vit_hist["test_acc"]) if a >= 70), NUM_EPOCHS)
119
+
120
+ print(f"\n{'':<25} {'SimpleCNN':>12} {'ViT':>12}")
121
+ print("-" * 50)
122
+ print(f"{'Parameters':<25} {cnn_params:>10,d} {vit_params:>10,d}")
123
+ print(f"{'Test Accuracy':<25} {cnn_acc:>10.2f}% {vit_acc:>10.2f}%")
124
+ print(f"{'Total Training Time':<25} {cnn_time:>8.1f}s {vit_time:>8.1f}s")
125
+ print(f"{'Epochs to 70% Acc':<25} {cnn_70:>10d} {vit_70:>10d}")
126
+ print("-" * 50)
127
+
128
+ winner = "SimpleCNN" if cnn_acc > vit_acc else "ViT" if vit_acc > cnn_acc else "Tie"
129
+ print(f"\nWinner: {winner}")
130
+ return winner
131
+
132
+
133
+ def save_plot(cnn_hist, vit_hist):
134
+ import matplotlib.pyplot as plt
135
+
136
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
137
+
138
+ ax1.plot(cnn_hist["loss"], label="SimpleCNN", marker="o")
139
+ ax1.plot(vit_hist["loss"], label="ViT", marker="s")
140
+ ax1.set_xlabel("Epoch"); ax1.set_ylabel("Loss"); ax1.set_title("Training Loss")
141
+ ax1.legend(); ax1.grid(True)
142
+
143
+ ax2.plot(cnn_hist["test_acc"], label="SimpleCNN", marker="o")
144
+ ax2.plot(vit_hist["test_acc"], label="ViT", marker="s")
145
+ ax2.set_xlabel("Epoch"); ax2.set_ylabel("Test Accuracy (%)"); ax2.set_title("Test Accuracy")
146
+ ax2.legend(); ax2.grid(True)
147
+
148
+ plt.tight_layout()
149
+ plt.savefig("benchmark_cnn_vs_vit.png", dpi=150)
150
+ print(f"\nPlot saved to benchmark_cnn_vs_vit.png")
151
+
152
+
153
+ def main():
154
+ device = torch.device("mps" if torch.backends.mps.is_available() else "cpu")
155
+ print(f"Device: {device}")
156
+ torch.set_num_threads(4)
157
+
158
+ train_loader, test_loader = load_data()
159
+ print(f"Data loaded: {len(train_loader.dataset):,} train, {len(test_loader.dataset):,} test")
160
+
161
+ # Train SimpleCNN.
162
+ print("\n── Training SimpleCNN ──")
163
+ cnn_model = SimpleCNN(num_classes=10)
164
+ cnn_params = sum(p.numel() for p in cnn_model.parameters())
165
+ cnn_hist = train_model(cnn_model, train_loader, test_loader, device, "SimpleCNN")
166
+
167
+ # Train ViT.
168
+ print("\n── Training ViT ──")
169
+ vit_model = ViT(d_model=128, n_heads=4, n_layers=4, d_ff=512,
170
+ patch_size=4, num_classes=10, dropout=0.1)
171
+ vit_params = sum(p.numel() for p in vit_model.parameters())
172
+ vit_hist = train_model(vit_model, train_loader, test_loader, device, "ViT")
173
+
174
+ # Output.
175
+ print_table(cnn_hist, vit_hist, cnn_params, vit_params)
176
+ save_plot(cnn_hist, vit_hist)
177
+
178
+
179
+ if __name__ == "__main__":
180
+ main()