ASomeoneWhoInterestedWithAI commited on
Commit
20b63f9
·
verified ·
1 Parent(s): 8d2c280

Upload 3 files

Browse files
Files changed (3) hide show
  1. LookThem_V76_LiteResidualClassifier.pth +3 -0
  2. inference.py +609 -0
  3. train.py +731 -0
LookThem_V76_LiteResidualClassifier.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f7760fabb39018eeab7b0b493bae68bd14240dd7b64ded078453735c9749a2de
3
+ size 10293483
inference.py ADDED
@@ -0,0 +1,609 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import math
4
+ from PIL import Image
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+
10
+ import torchvision.transforms as transforms
11
+
12
+ # ============================================================
13
+ # CONFIG
14
+ # ============================================================
15
+
16
+ MODEL_PATH = "LookThem_V76_LiteResidualClassifier.pth"
17
+
18
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
19
+
20
+ # ============================================================
21
+ # IMAGENET-100 LABELS
22
+ # ============================================================
23
+
24
+ # kalau punya labels asli tinggal ganti
25
+ CLASS_NAMES = [
26
+
27
+ "bonnet, poke bonnet",
28
+ "green mamba",
29
+ "langur",
30
+ "Doberman, Doberman pinscher",
31
+ "gyromitra",
32
+ "Saluki, gazelle hound",
33
+ "vacuum, vacuum cleaner",
34
+ "window screen",
35
+ "cocktail shaker",
36
+ "garden spider, Aranea diademata",
37
+ "garter snake, grass snake",
38
+ "carbonara",
39
+ "pineapple, ananas",
40
+ "computer keyboard, keypad",
41
+ "tripod",
42
+ "komondor",
43
+ "American lobster, Northern lobster, Maine lobster, Homarus americanus",
44
+ "bannister, banister, balustrade, balusters, handrail",
45
+ "honeycomb",
46
+ "tile roof",
47
+ "papillon",
48
+ "boathouse",
49
+ "stinkhorn, carrion fungus",
50
+ "jean, blue jean, denim",
51
+ "Chihuahua",
52
+ "Chesapeake Bay retriever",
53
+ "robin, American robin, Turdus migratorius",
54
+ "tub, vat",
55
+ "Great Dane",
56
+ "rotisserie",
57
+ "bottlecap",
58
+ "throne",
59
+ "little blue heron, Egretta caerulea",
60
+ "rock crab, Cancer irroratus",
61
+ "Rottweiler",
62
+ "lorikeet",
63
+ "Gila monster, Heloderma suspectum",
64
+ "head cabbage",
65
+ "car wheel",
66
+ "coyote, prairie wolf, brush wolf, Canis latrans",
67
+ "moped",
68
+ "milk can",
69
+ "mixing bowl",
70
+ "toy terrier",
71
+ "chocolate sauce, chocolate syrup",
72
+ "rocking chair, rocker",
73
+ "wing",
74
+ "park bench",
75
+ "ambulance",
76
+ "football helmet",
77
+ "leafhopper",
78
+ "cauliflower",
79
+ "pirate, pirate ship",
80
+ "purse",
81
+ "hare",
82
+ "lampshade, lamp shade",
83
+ "fiddler crab",
84
+ "standard poodle",
85
+ "Shih-Tzu",
86
+ "pedestal, plinth, footstall",
87
+ "gibbon, Hylobates lar",
88
+ "safety pin",
89
+ "English foxhound",
90
+ "chime, bell, gong",
91
+ "American Staffordshire terrier, Staffordshire terrier, American pit bull terrier, pit bull terrier",
92
+ "bassinet",
93
+ "wild boar, boar, Sus scrofa",
94
+ "theater curtain, theatre curtain",
95
+ "dung beetle",
96
+ "hognose snake, puff adder, sand viper",
97
+ "Mexican hairless",
98
+ "mortarboard",
99
+ "Walker hound, Walker foxhound",
100
+ "red fox, Vulpes vulpes",
101
+ "modem",
102
+ "slide rule, slipstick",
103
+ "walking stick, walkingstick, stick insect",
104
+ "cinema, movie theater, movie theatre, movie house, picture palace",
105
+ "meerkat, mierkat",
106
+ "kuvasz",
107
+ "obelisk",
108
+ "harmonica, mouth organ, harp, mouth harp",
109
+ "sarong",
110
+ "mousetrap",
111
+ "hard disc, hard disk, fixed disk",
112
+ "American coot, marsh hen, mud hen, water hen, Fulica americana",
113
+ "reel",
114
+ "pickup, pickup truck",
115
+ "iron, smoothing iron",
116
+ "tabby, tabby cat",
117
+ "ski mask",
118
+ "vizsla, Hungarian pointer",
119
+ "laptop, laptop computer",
120
+ "stretcher",
121
+ "Dutch oven",
122
+ "African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus",
123
+ "boxer",
124
+ "gasmask, respirator, gas helmet",
125
+ "goose",
126
+ "borzoi, Russian wolfhound"
127
+
128
+ ]
129
+
130
+ # ============================================================
131
+ # TRANSFORM
132
+ # ============================================================
133
+
134
+ transform = transforms.Compose([
135
+ transforms.Lambda(lambda img: img.convert("RGB")),
136
+ transforms.Resize((256, 256)),
137
+ transforms.ToTensor(),
138
+ transforms.Normalize(
139
+ mean=(0.485, 0.456, 0.406),
140
+ std=(0.229, 0.224, 0.225)
141
+ )
142
+ ])
143
+
144
+ # ============================================================
145
+ # LOOKTHEM LAYER
146
+ # ============================================================
147
+
148
+ class LookThemLayer(nn.Module):
149
+
150
+ def __init__(self, num_tokens, in_features, hidden_dim):
151
+ super().__init__()
152
+
153
+ self.num_tokens = num_tokens
154
+
155
+ self.mod1_w1 = nn.Parameter(
156
+ torch.randn(num_tokens, in_features, hidden_dim)
157
+ )
158
+
159
+ self.mod1_b1 = nn.Parameter(
160
+ torch.zeros(num_tokens, hidden_dim)
161
+ )
162
+
163
+ self.mod1_w2 = nn.Parameter(
164
+ torch.randn(num_tokens, hidden_dim, 1)
165
+ )
166
+
167
+ self.mod1_b2 = nn.Parameter(
168
+ torch.zeros(num_tokens, 1)
169
+ )
170
+
171
+ self.mod2_w1 = nn.Parameter(
172
+ torch.randn(num_tokens, in_features, hidden_dim)
173
+ )
174
+
175
+ self.mod2_b1 = nn.Parameter(
176
+ torch.zeros(num_tokens, hidden_dim)
177
+ )
178
+
179
+ self.mod2_w2 = nn.Parameter(
180
+ torch.randn(num_tokens, hidden_dim, 1)
181
+ )
182
+
183
+ self.mod2_b2 = nn.Parameter(
184
+ torch.zeros(num_tokens, 1)
185
+ )
186
+
187
+ self.trans_w = nn.Parameter(
188
+ torch.randn(num_tokens, 1, 1)
189
+ )
190
+
191
+ self.trans_b = nn.Parameter(
192
+ torch.zeros(num_tokens, 1)
193
+ )
194
+
195
+ self._init_weights()
196
+
197
+ def _init_weights(self):
198
+
199
+ for w in [
200
+ self.mod1_w1,
201
+ self.mod2_w1,
202
+ self.mod1_w2,
203
+ self.mod2_w2,
204
+ self.trans_w
205
+ ]:
206
+ nn.init.kaiming_uniform_(w, a=math.sqrt(5))
207
+
208
+ def forward(self, x):
209
+
210
+ N = self.num_tokens
211
+
212
+ # ====================================================
213
+ # MOD 1
214
+ # ====================================================
215
+
216
+ h1 = (
217
+ torch.einsum(
218
+ 'bti,tij->btj',
219
+ x,
220
+ self.mod1_w1
221
+ )
222
+ + self.mod1_b1
223
+ )
224
+
225
+ out_m1 = (
226
+ torch.einsum(
227
+ 'btj,tjk->btk',
228
+ F.gelu(h1),
229
+ self.mod1_w2
230
+ )
231
+ + self.mod1_b2
232
+ )
233
+
234
+ # ====================================================
235
+ # MOD 2
236
+ # ====================================================
237
+
238
+ h2 = (
239
+ torch.einsum(
240
+ 'bti,tij->btj',
241
+ x,
242
+ self.mod2_w1
243
+ )
244
+ + self.mod2_b1
245
+ )
246
+
247
+ out_m2 = (
248
+ torch.einsum(
249
+ 'btj,tjk->btk',
250
+ F.gelu(h2),
251
+ self.mod2_w2
252
+ )
253
+ + self.mod2_b2
254
+ )
255
+
256
+ # ====================================================
257
+ # COMPARISON
258
+ # ====================================================
259
+
260
+ out_m2_safe = out_m2 + 1e-5
261
+
262
+ compare = torch.tanh(
263
+ out_m1.unsqueeze(2)
264
+ / out_m2_safe.unsqueeze(1)
265
+ )
266
+
267
+ compare2 = torch.tanh(
268
+ out_m1.unsqueeze(1)
269
+ / out_m2_safe.unsqueeze(2)
270
+ )
271
+
272
+ # ====================================================
273
+ # TRANSFORM
274
+ # ====================================================
275
+
276
+ bias_reshaped = self.trans_b.view(1, 1, N, 1)
277
+
278
+ trans_compare = (
279
+ torch.einsum(
280
+ 'bije,jef->bijf',
281
+ compare,
282
+ self.trans_w
283
+ )
284
+ + bias_reshaped
285
+ )
286
+
287
+ trans_compare2 = (
288
+ torch.einsum(
289
+ 'bije,jef->bijf',
290
+ compare2,
291
+ self.trans_w
292
+ )
293
+ + bias_reshaped
294
+ )
295
+
296
+ # ====================================================
297
+ # INTERACTION
298
+ # ====================================================
299
+
300
+ interaksi = (
301
+ trans_compare * x.unsqueeze(2)
302
+ + trans_compare2 * x.unsqueeze(1)
303
+ ) / 2
304
+
305
+ mask = 1.0 - torch.eye(N, device=x.device)
306
+
307
+ interaksi_masked = (
308
+ interaksi
309
+ * mask.view(1, N, N, 1)
310
+ )
311
+
312
+ return interaksi_masked.sum(dim=2) / (N - 1.0)
313
+
314
+ # ============================================================
315
+ # BACKBONE
316
+ # ============================================================
317
+
318
+ class LookThemBackbone(nn.Module):
319
+
320
+ def __init__(self):
321
+ super().__init__()
322
+
323
+ self.stream_a = nn.Sequential(
324
+
325
+ nn.Conv2d(3, 16, 3, stride=2, padding=1),
326
+ nn.BatchNorm2d(16),
327
+ nn.GELU(),
328
+
329
+ nn.Conv2d(16, 32, 3, stride=2, padding=1),
330
+ nn.BatchNorm2d(32),
331
+ nn.GELU(),
332
+
333
+ nn.Conv2d(32, 64, 3, stride=2, padding=1),
334
+ nn.BatchNorm2d(64),
335
+ nn.GELU(),
336
+
337
+ nn.Conv2d(64, 64, 3, stride=2, padding=1),
338
+ nn.BatchNorm2d(64),
339
+ nn.GELU(),
340
+
341
+ nn.AdaptiveMaxPool2d((8, 8))
342
+ )
343
+
344
+ self.stream_b = nn.Sequential(
345
+
346
+ nn.Conv2d(3, 16, 3, stride=1, padding=1),
347
+ nn.BatchNorm2d(16),
348
+ nn.GELU(),
349
+
350
+ nn.Conv2d(16, 32, 3, stride=1, padding=1),
351
+ nn.BatchNorm2d(32),
352
+ nn.GELU(),
353
+
354
+ nn.Conv2d(32, 64, 3, stride=2, padding=1),
355
+ nn.BatchNorm2d(64),
356
+ nn.GELU(),
357
+
358
+ nn.Conv2d(64, 64, 3, stride=1, padding=1),
359
+ nn.BatchNorm2d(64),
360
+ nn.GELU(),
361
+
362
+ nn.AdaptiveMaxPool2d((8, 8))
363
+ )
364
+
365
+ self.lookthemA = LookThemLayer(
366
+ num_tokens=64,
367
+ in_features=64,
368
+ hidden_dim=32
369
+ )
370
+
371
+ self.lookthemB = LookThemLayer(
372
+ num_tokens=64,
373
+ in_features=64,
374
+ hidden_dim=32
375
+ )
376
+
377
+ self.lookthem = LookThemLayer(
378
+ num_tokens=64,
379
+ in_features=128,
380
+ hidden_dim=32
381
+ )
382
+
383
+ self.compressor = nn.Conv1d(
384
+ 128,
385
+ 64,
386
+ kernel_size=1
387
+ )
388
+
389
+ def forward(self, x):
390
+
391
+ B = x.size(0)
392
+
393
+ # ====================================================
394
+ # STREAM A
395
+ # ====================================================
396
+
397
+ feat_a = self.stream_a(x)
398
+
399
+ feat_a = (
400
+ feat_a
401
+ .view(B, 64, 64)
402
+ .transpose(1, 2)
403
+ )
404
+
405
+ feat_a = self.lookthemA(feat_a)
406
+
407
+ # ====================================================
408
+ # STREAM B
409
+ # ====================================================
410
+
411
+ feat_b = self.stream_b(x)
412
+
413
+ feat_b = (
414
+ feat_b
415
+ .view(B, 64, 64)
416
+ .transpose(1, 2)
417
+ )
418
+
419
+ feat_b = self.lookthemB(feat_b)
420
+
421
+ # ====================================================
422
+ # COMBINE
423
+ # ====================================================
424
+
425
+ combined = torch.cat(
426
+ [feat_a, feat_b],
427
+ dim=2
428
+ )
429
+
430
+ out = self.lookthem(combined)
431
+
432
+ out = out.transpose(1, 2)
433
+
434
+ compressed = self.compressor(out)
435
+
436
+ return compressed
437
+
438
+ # ============================================================
439
+ # CLASSIFIER
440
+ # ============================================================
441
+
442
+ class LiteResidualBlock(nn.Module):
443
+
444
+ def __init__(self, dim, dropout=0.05):
445
+ super().__init__()
446
+
447
+ self.block = nn.Sequential(
448
+
449
+ nn.Linear(dim, dim),
450
+ nn.GELU(),
451
+ nn.Dropout(dropout),
452
+
453
+ nn.Linear(dim, dim)
454
+ )
455
+
456
+ self.norm = nn.LayerNorm(dim)
457
+
458
+ def forward(self, x):
459
+
460
+ residual = x
461
+
462
+ x = self.block(x)
463
+
464
+ x = x + residual
465
+
466
+ x = self.norm(x)
467
+
468
+ return x
469
+
470
+ class EfficientResidualClassifier(nn.Module):
471
+
472
+ def __init__(self):
473
+ super().__init__()
474
+
475
+ self.flatten = nn.Flatten()
476
+
477
+ self.input_proj = nn.Sequential(
478
+
479
+ nn.Linear(4096, 256),
480
+ nn.GELU(),
481
+ nn.Dropout(0.08)
482
+ )
483
+
484
+ self.res1 = LiteResidualBlock(256)
485
+ self.res2 = LiteResidualBlock(256)
486
+
487
+ self.head = nn.Sequential(
488
+
489
+ nn.Linear(256, 128),
490
+ nn.GELU(),
491
+
492
+ nn.Linear(128, 100)
493
+ )
494
+
495
+ def forward(self, x):
496
+
497
+ x = self.flatten(x)
498
+
499
+ x = self.input_proj(x)
500
+
501
+ x = self.res1(x)
502
+
503
+ x = self.res2(x)
504
+
505
+ x = self.head(x)
506
+
507
+ return x
508
+
509
+ # ============================================================
510
+ # FULL MODEL
511
+ # ============================================================
512
+
513
+ class FullModel(nn.Module):
514
+
515
+ def __init__(self):
516
+ super().__init__()
517
+
518
+ self.backbone = LookThemBackbone()
519
+
520
+ self.classifier = EfficientResidualClassifier()
521
+
522
+ def forward(self, x):
523
+
524
+ feat = self.backbone(x)
525
+
526
+ out = self.classifier(feat)
527
+
528
+ return out
529
+
530
+ # ============================================================
531
+ # LOAD MODEL
532
+ # ============================================================
533
+
534
+ print("🧠 Loading model...")
535
+
536
+ model = FullModel().to(device)
537
+
538
+ state_dict = torch.load(
539
+ MODEL_PATH,
540
+ map_location=device
541
+ )
542
+
543
+ model.load_state_dict(state_dict)
544
+
545
+ model.eval()
546
+
547
+ print("✅ Model loaded!")
548
+
549
+ # ============================================================
550
+ # PREDICTION FUNCTION
551
+ # ============================================================
552
+
553
+ def predict_image(image_path):
554
+
555
+ img = Image.open(image_path)
556
+
557
+ x = transform(img)
558
+
559
+ x = x.unsqueeze(0).to(device)
560
+
561
+ with torch.no_grad():
562
+
563
+ output = model(x)
564
+
565
+ probs = torch.softmax(output, dim=1)
566
+
567
+ top5_prob, top5_idx = torch.topk(probs, 5)
568
+
569
+ print("\n🏆 TOP 5 PREDICTIONS:\n")
570
+
571
+ for rank in range(5):
572
+
573
+ idx = top5_idx[0][rank].item()
574
+
575
+ prob = top5_prob[0][rank].item() * 100
576
+
577
+ print(
578
+ f"{rank+1}. "
579
+ f"{CLASS_NAMES[idx]} "
580
+ f"({prob:.2f}%)"
581
+ )
582
+
583
+ # ============================================================
584
+ # INTERACTIVE LOOP
585
+ # ============================================================
586
+
587
+ print("\n===================================")
588
+ print("🧠 LookThem V7.6 Inference")
589
+ print("Type image path")
590
+ print("Type 'exit' to quit")
591
+ print("===================================\n")
592
+
593
+ while True:
594
+
595
+ image_path = input("📷 Image Path: ")
596
+
597
+ if image_path.lower() == "exit":
598
+ print("\n👋 Exiting...")
599
+ break
600
+
601
+ if not os.path.exists(image_path):
602
+ print("❌ File not found!\n")
603
+ continue
604
+
605
+ try:
606
+ predict_image(image_path)
607
+
608
+ except Exception as e:
609
+ print(f"\n❌ Error: {e}\n")
train.py ADDED
@@ -0,0 +1,731 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================================
2
+ # LOOKTHEM V7.6 FULL TRAINING + INFERENCE
3
+ # Backbone + Lite Residual Classifier
4
+ # ============================================================
5
+
6
+ import os
7
+ import io
8
+ import math
9
+ from PIL import Image
10
+
11
+ import torch
12
+ import torch.nn as nn
13
+ import torch.nn.functional as F
14
+ import torch.optim as optim
15
+
16
+ from torch.utils.data import Dataset, DataLoader
17
+
18
+ import torchvision.transforms as transforms
19
+
20
+ from datasets import load_dataset
21
+
22
+ # ============================================================
23
+ # CONFIG
24
+ # ============================================================
25
+
26
+ DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
27
+
28
+ BATCH_SIZE_TRAIN = 96
29
+ BATCH_SIZE_VAL = 32
30
+
31
+ EPOCHS = 20
32
+
33
+ LR = 1e-3
34
+ WEIGHT_DECAY = 1e-4
35
+
36
+ MODEL_SAVE_PATH = "LookThem_V76_Full_LiteResidual.pth"
37
+
38
+ # ============================================================
39
+ # TRANSFORM
40
+ # ============================================================
41
+
42
+ transform_train = transforms.Compose([
43
+ transforms.Lambda(lambda img: img.convert("RGB")),
44
+ transforms.Resize((256, 256)),
45
+ transforms.RandomHorizontalFlip(),
46
+ transforms.ToTensor(),
47
+ transforms.Normalize(
48
+ (0.485, 0.456, 0.406),
49
+ (0.229, 0.224, 0.225)
50
+ )
51
+ ])
52
+
53
+ transform_val = transforms.Compose([
54
+ transforms.Lambda(lambda img: img.convert("RGB")),
55
+ transforms.Resize((256, 256)),
56
+ transforms.ToTensor(),
57
+ transforms.Normalize(
58
+ (0.485, 0.456, 0.406),
59
+ (0.229, 0.224, 0.225)
60
+ )
61
+ ])
62
+
63
+ # ============================================================
64
+ # DATASET
65
+ # ============================================================
66
+
67
+ class ImageNet100ParquetDataset(Dataset):
68
+
69
+ def __init__(self, hf_subset, transform=None):
70
+
71
+ self.dataset = hf_subset
72
+ self.transform = transform
73
+
74
+ def __getitem__(self, index):
75
+
76
+ row = self.dataset[index]
77
+
78
+ img_data = row["image"]
79
+
80
+ if isinstance(img_data, dict) and "bytes" in img_data:
81
+ img = Image.open(io.BytesIO(img_data["bytes"]))
82
+
83
+ elif isinstance(img_data, Image.Image):
84
+ img = img_data
85
+
86
+ else:
87
+ img = Image.open(io.BytesIO(img_data))
88
+
89
+ label = row["label"]
90
+
91
+ if self.transform:
92
+ img = self.transform(img)
93
+
94
+ return img, label
95
+
96
+ def __len__(self):
97
+
98
+ return len(self.dataset)
99
+
100
+ # ============================================================
101
+ # LOAD DATASET
102
+ # ============================================================
103
+
104
+ print("📡 Loading ImageNet-100...")
105
+
106
+ raw_train = load_dataset(
107
+ "clane9/imagenet-100",
108
+ split="train"
109
+ )
110
+
111
+ raw_val = load_dataset(
112
+ "clane9/imagenet-100",
113
+ split="validation"
114
+ )
115
+
116
+ train_dataset = ImageNet100ParquetDataset(
117
+ raw_train,
118
+ transform=transform_train
119
+ )
120
+
121
+ val_dataset = ImageNet100ParquetDataset(
122
+ raw_val,
123
+ transform=transform_val
124
+ )
125
+
126
+ train_loader = DataLoader(
127
+ train_dataset,
128
+ batch_size=BATCH_SIZE_TRAIN,
129
+ shuffle=True,
130
+ num_workers=2,
131
+ pin_memory=True
132
+ )
133
+
134
+ val_loader = DataLoader(
135
+ val_dataset,
136
+ batch_size=BATCH_SIZE_VAL,
137
+ shuffle=False,
138
+ num_workers=2,
139
+ pin_memory=True
140
+ )
141
+
142
+ # ============================================================
143
+ # LOOKTHEM LAYER
144
+ # ============================================================
145
+
146
+ class LookThemLayer(nn.Module):
147
+
148
+ def __init__(self, num_tokens, in_features, hidden_dim):
149
+
150
+ super().__init__()
151
+
152
+ self.num_tokens = num_tokens
153
+
154
+ self.mod1_w1 = nn.Parameter(
155
+ torch.randn(num_tokens, in_features, hidden_dim)
156
+ )
157
+
158
+ self.mod1_b1 = nn.Parameter(
159
+ torch.zeros(num_tokens, hidden_dim)
160
+ )
161
+
162
+ self.mod1_w2 = nn.Parameter(
163
+ torch.randn(num_tokens, hidden_dim, 1)
164
+ )
165
+
166
+ self.mod1_b2 = nn.Parameter(
167
+ torch.zeros(num_tokens, 1)
168
+ )
169
+
170
+ self.mod2_w1 = nn.Parameter(
171
+ torch.randn(num_tokens, in_features, hidden_dim)
172
+ )
173
+
174
+ self.mod2_b1 = nn.Parameter(
175
+ torch.zeros(num_tokens, hidden_dim)
176
+ )
177
+
178
+ self.mod2_w2 = nn.Parameter(
179
+ torch.randn(num_tokens, hidden_dim, 1)
180
+ )
181
+
182
+ self.mod2_b2 = nn.Parameter(
183
+ torch.zeros(num_tokens, 1)
184
+ )
185
+
186
+ self.trans_w = nn.Parameter(
187
+ torch.randn(num_tokens, 1, 1)
188
+ )
189
+
190
+ self.trans_b = nn.Parameter(
191
+ torch.zeros(num_tokens, 1)
192
+ )
193
+
194
+ self._init_weights()
195
+
196
+ def _init_weights(self):
197
+
198
+ for w in [
199
+ self.mod1_w1,
200
+ self.mod2_w1,
201
+ self.mod1_w2,
202
+ self.mod2_w2,
203
+ self.trans_w
204
+ ]:
205
+ nn.init.kaiming_uniform_(w, a=math.sqrt(5))
206
+
207
+ def forward(self, x):
208
+
209
+ N = self.num_tokens
210
+
211
+ h1 = (
212
+ torch.einsum(
213
+ "bti,tij->btj",
214
+ x,
215
+ self.mod1_w1
216
+ )
217
+ + self.mod1_b1
218
+ )
219
+
220
+ out_m1 = (
221
+ torch.einsum(
222
+ "btj,tjk->btk",
223
+ F.gelu(h1),
224
+ self.mod1_w2
225
+ )
226
+ + self.mod1_b2
227
+ )
228
+
229
+ h2 = (
230
+ torch.einsum(
231
+ "bti,tij->btj",
232
+ x,
233
+ self.mod2_w1
234
+ )
235
+ + self.mod2_b1
236
+ )
237
+
238
+ out_m2 = (
239
+ torch.einsum(
240
+ "btj,tjk->btk",
241
+ F.gelu(h2),
242
+ self.mod2_w2
243
+ )
244
+ + self.mod2_b2
245
+ )
246
+
247
+ out_m2_safe = out_m2 + 1e-5
248
+
249
+ compare = torch.tanh(
250
+ out_m1.unsqueeze(2)
251
+ / out_m2_safe.unsqueeze(1)
252
+ )
253
+
254
+ compare2 = torch.tanh(
255
+ out_m1.unsqueeze(1)
256
+ / out_m2_safe.unsqueeze(2)
257
+ )
258
+
259
+ bias_reshaped = self.trans_b.view(
260
+ 1, 1, N, 1
261
+ )
262
+
263
+ trans_compare = (
264
+ torch.einsum(
265
+ "bije,jef->bijf",
266
+ compare,
267
+ self.trans_w
268
+ )
269
+ + bias_reshaped
270
+ )
271
+
272
+ trans_compare2 = (
273
+ torch.einsum(
274
+ "bije,jef->bijf",
275
+ compare2,
276
+ self.trans_w
277
+ )
278
+ + bias_reshaped
279
+ )
280
+
281
+ interaksi = (
282
+ trans_compare * x.unsqueeze(2)
283
+ + trans_compare2 * x.unsqueeze(1)
284
+ ) / 2
285
+
286
+ mask = 1.0 - torch.eye(
287
+ N,
288
+ device=x.device
289
+ )
290
+
291
+ interaksi_masked = (
292
+ interaksi
293
+ * mask.view(1, N, N, 1)
294
+ )
295
+
296
+ return interaksi_masked.sum(dim=2) / (N - 1.0)
297
+
298
+ # ============================================================
299
+ # LITE RESIDUAL BLOCK
300
+ # ============================================================
301
+
302
+ class LiteResidualBlock(nn.Module):
303
+
304
+ def __init__(self, dim, dropout=0.05):
305
+
306
+ super().__init__()
307
+
308
+ self.block = nn.Sequential(
309
+
310
+ nn.Linear(dim, dim),
311
+ nn.GELU(),
312
+ nn.Dropout(dropout),
313
+
314
+ nn.Linear(dim, dim)
315
+ )
316
+
317
+ self.norm = nn.LayerNorm(dim)
318
+
319
+ def forward(self, x):
320
+
321
+ residual = x
322
+
323
+ x = self.block(x)
324
+
325
+ x = x + residual
326
+
327
+ x = self.norm(x)
328
+
329
+ return x
330
+
331
+ # ============================================================
332
+ # FULL MODEL
333
+ # ============================================================
334
+
335
+ class LookThemV76LiteResidual(nn.Module):
336
+
337
+ def __init__(self):
338
+
339
+ super().__init__()
340
+
341
+ # ====================================================
342
+ # STREAM A
343
+ # ====================================================
344
+
345
+ self.stream_a = nn.Sequential(
346
+
347
+ nn.Conv2d(
348
+ 3,
349
+ 16,
350
+ kernel_size=3,
351
+ stride=2,
352
+ padding=1
353
+ ),
354
+
355
+ nn.BatchNorm2d(16),
356
+ nn.GELU(),
357
+
358
+ nn.Conv2d(
359
+ 16,
360
+ 32,
361
+ kernel_size=3,
362
+ stride=2,
363
+ padding=1
364
+ ),
365
+
366
+ nn.BatchNorm2d(32),
367
+ nn.GELU(),
368
+
369
+ nn.Conv2d(
370
+ 32,
371
+ 64,
372
+ kernel_size=3,
373
+ stride=2,
374
+ padding=1
375
+ ),
376
+
377
+ nn.BatchNorm2d(64),
378
+ nn.GELU(),
379
+
380
+ nn.Conv2d(
381
+ 64,
382
+ 64,
383
+ kernel_size=3,
384
+ stride=2,
385
+ padding=1
386
+ ),
387
+
388
+ nn.BatchNorm2d(64),
389
+ nn.GELU(),
390
+
391
+ nn.AdaptiveMaxPool2d((8, 8))
392
+ )
393
+
394
+ # ====================================================
395
+ # STREAM B
396
+ # ====================================================
397
+
398
+ self.stream_b = nn.Sequential(
399
+
400
+ nn.Conv2d(
401
+ 3,
402
+ 16,
403
+ kernel_size=3,
404
+ stride=1,
405
+ padding=1
406
+ ),
407
+
408
+ nn.BatchNorm2d(16),
409
+ nn.GELU(),
410
+
411
+ nn.Conv2d(
412
+ 16,
413
+ 32,
414
+ kernel_size=3,
415
+ stride=1,
416
+ padding=1
417
+ ),
418
+
419
+ nn.BatchNorm2d(32),
420
+ nn.GELU(),
421
+
422
+ nn.Conv2d(
423
+ 32,
424
+ 64,
425
+ kernel_size=3,
426
+ stride=2,
427
+ padding=1
428
+ ),
429
+
430
+ nn.BatchNorm2d(64),
431
+ nn.GELU(),
432
+
433
+ nn.Conv2d(
434
+ 64,
435
+ 64,
436
+ kernel_size=3,
437
+ stride=1,
438
+ padding=1
439
+ ),
440
+
441
+ nn.BatchNorm2d(64),
442
+ nn.GELU(),
443
+
444
+ nn.AdaptiveMaxPool2d((8, 8))
445
+ )
446
+
447
+ # ====================================================
448
+ # LOOKTHEM
449
+ # ====================================================
450
+
451
+ self.lookthemA = LookThemLayer(
452
+ num_tokens=64,
453
+ in_features=64,
454
+ hidden_dim=32
455
+ )
456
+
457
+ self.lookthemB = LookThemLayer(
458
+ num_tokens=64,
459
+ in_features=64,
460
+ hidden_dim=32
461
+ )
462
+
463
+ self.lookthem = LookThemLayer(
464
+ num_tokens=64,
465
+ in_features=128,
466
+ hidden_dim=32
467
+ )
468
+
469
+ self.compressor = nn.Conv1d(
470
+ 128,
471
+ 64,
472
+ kernel_size=1
473
+ )
474
+
475
+ self.imageCorrupter = nn.Dropout(0.1)
476
+
477
+ # ====================================================
478
+ # CLASSIFIER
479
+ # ====================================================
480
+
481
+ self.flatten = nn.Flatten()
482
+
483
+ self.input_proj = nn.Sequential(
484
+
485
+ nn.Linear(4096, 256),
486
+ nn.GELU(),
487
+ nn.Dropout(0.08)
488
+ )
489
+
490
+ self.res1 = LiteResidualBlock(256, 0.05)
491
+
492
+ self.res2 = LiteResidualBlock(256, 0.05)
493
+
494
+ self.head = nn.Sequential(
495
+
496
+ nn.Linear(256, 128),
497
+ nn.GELU(),
498
+
499
+ nn.Linear(128, 100)
500
+ )
501
+
502
+ def extract_features(self, x):
503
+
504
+ batch_size = x.size(0)
505
+
506
+ # ====================================================
507
+ # STREAM A
508
+ # ====================================================
509
+
510
+ feat_a = self.stream_a(x)
511
+
512
+ feat_a_tokens = feat_a.view(
513
+ batch_size,
514
+ 64,
515
+ 64
516
+ ).transpose(1, 2)
517
+
518
+ feat_a_tokens = self.imageCorrupter(
519
+ feat_a_tokens
520
+ )
521
+
522
+ feat_a_lt = self.lookthemA(
523
+ feat_a_tokens
524
+ )
525
+
526
+ # ====================================================
527
+ # STREAM B
528
+ # ====================================================
529
+
530
+ feat_b = self.stream_b(x)
531
+
532
+ feat_b_tokens = feat_b.view(
533
+ batch_size,
534
+ 64,
535
+ 64
536
+ ).transpose(1, 2)
537
+
538
+ feat_b_tokens = self.imageCorrupter(
539
+ feat_b_tokens
540
+ )
541
+
542
+ feat_b_lt = self.lookthemB(
543
+ feat_b_tokens
544
+ )
545
+
546
+ # ====================================================
547
+ # COMBINE
548
+ # ====================================================
549
+
550
+ tokens_combined = torch.cat(
551
+ [feat_a_lt, feat_b_lt],
552
+ dim=2
553
+ )
554
+
555
+ out_lookthem = self.lookthem(
556
+ tokens_combined
557
+ )
558
+
559
+ out_lookthem = out_lookthem.transpose(1, 2)
560
+
561
+ compressed = self.compressor(
562
+ out_lookthem
563
+ )
564
+
565
+ return compressed
566
+
567
+ def forward(self, x):
568
+
569
+ x = self.extract_features(x)
570
+
571
+ x = self.flatten(x)
572
+
573
+ x = self.input_proj(x)
574
+
575
+ x = self.res1(x)
576
+
577
+ x = self.res2(x)
578
+
579
+ x = self.head(x)
580
+
581
+ return x
582
+
583
+ # ============================================================
584
+ # MODEL INIT
585
+ # ============================================================
586
+
587
+ model = LookThemV76LiteResidual().to(DEVICE)
588
+
589
+ # ============================================================
590
+ # PARAMETER COUNT
591
+ # ============================================================
592
+
593
+ total_params = sum(
594
+ p.numel()
595
+ for p in model.parameters()
596
+ )
597
+
598
+ print(f"\n🧠 Total Parameters : {total_params:,}")
599
+
600
+ size_mb = total_params * 4 / (1024 * 1024)
601
+
602
+ print(f"📦 Estimated Size : {size_mb:.2f} MB")
603
+
604
+ # ============================================================
605
+ # LOSS & OPTIMIZER
606
+ # ============================================================
607
+
608
+ criterion = nn.CrossEntropyLoss()
609
+
610
+ optimizer = optim.AdamW(
611
+ model.parameters(),
612
+ lr=LR,
613
+ weight_decay=WEIGHT_DECAY
614
+ )
615
+
616
+ scheduler = optim.lr_scheduler.CosineAnnealingLR(
617
+ optimizer,
618
+ T_max=EPOCHS
619
+ )
620
+
621
+ # ============================================================
622
+ # TRAINING
623
+ # ============================================================
624
+
625
+ print("\n🚀 Training Started...\n")
626
+
627
+ for epoch in range(EPOCHS):
628
+
629
+ model.train()
630
+
631
+ total_loss = 0
632
+ correct = 0
633
+ total = 0
634
+
635
+ for step, (data, target) in enumerate(train_loader):
636
+
637
+ data = data.to(DEVICE)
638
+ target = target.to(DEVICE)
639
+
640
+ optimizer.zero_grad()
641
+
642
+ output = model(data)
643
+
644
+ loss = criterion(output, target)
645
+
646
+ loss.backward()
647
+
648
+ optimizer.step()
649
+
650
+ total_loss += loss.item()
651
+
652
+ _, predicted = output.max(1)
653
+
654
+ total += target.size(0)
655
+
656
+ correct += predicted.eq(target).sum().item()
657
+
658
+ if (step + 1) % 100 == 0:
659
+
660
+ print(
661
+ f"Epoch [{epoch+1:02d}/{EPOCHS}] "
662
+ f"| Step [{step+1}/{len(train_loader)}] "
663
+ f"| Loss: {loss.item():.4f}"
664
+ )
665
+
666
+ scheduler.step()
667
+
668
+ acc = 100. * correct / total
669
+
670
+ current_lr = optimizer.param_groups[0]["lr"]
671
+
672
+ print(
673
+ f"\n🏁 Epoch [{epoch+1:02d}/{EPOCHS}] "
674
+ f"| Loss: {total_loss / len(train_loader):.4f} "
675
+ f"| Train Acc: {acc:.2f}% "
676
+ f"| LR: {current_lr:.6f}\n"
677
+ )
678
+
679
+ # ============================================================
680
+ # VALIDATION
681
+ # ============================================================
682
+
683
+ print("\n🧪 Validation...\n")
684
+
685
+ model.eval()
686
+
687
+ val_loss = 0
688
+ val_correct = 0
689
+ val_total = 0
690
+
691
+ with torch.no_grad():
692
+
693
+ for data, target in val_loader:
694
+
695
+ data = data.to(DEVICE)
696
+ target = target.to(DEVICE)
697
+
698
+ output = model(data)
699
+
700
+ loss = criterion(output, target)
701
+
702
+ val_loss += loss.item()
703
+
704
+ _, predicted = output.max(1)
705
+
706
+ val_total += target.size(0)
707
+
708
+ val_correct += predicted.eq(target).sum().item()
709
+
710
+ val_acc = 100. * val_correct / val_total
711
+
712
+ print(
713
+ f"\n🏆 Validation Accuracy: {val_acc:.2f}%"
714
+ )
715
+
716
+ # ============================================================
717
+ # SAVE MODEL
718
+ # ============================================================
719
+
720
+ torch.save(
721
+ model.state_dict(),
722
+ MODEL_SAVE_PATH
723
+ )
724
+
725
+ real_size = os.path.getsize(
726
+ MODEL_SAVE_PATH
727
+ ) / (1024 * 1024)
728
+
729
+ print("\n💾 MODEL SAVED!")
730
+ print(f"📁 Path : {MODEL_SAVE_PATH}")
731
+ print(f"📦 Size : {real_size:.2f} MB")