ASomeoneWhoInterestedWithAI commited on
Commit
c1c7652
·
verified ·
1 Parent(s): cceed08

Create V5CleanedCode.md

Browse files
Files changed (1) hide show
  1. V5CleanedCode.md +854 -0
V5CleanedCode.md ADDED
@@ -0,0 +1,854 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cleaned Code
2
+ ```python
3
+ import os
4
+ import math
5
+ import zipfile
6
+ import urllib.request
7
+
8
+ import torch
9
+ import torch.nn as nn
10
+ import torch.nn.functional as F
11
+ import torch.optim as optim
12
+
13
+ from torch.utils.data import DataLoader
14
+ from torchvision import datasets, transforms
15
+
16
+
17
+ # =========================================================
18
+ # 1. TINY-IMAGENET DOWNLOAD + PREPARATION
19
+ # =========================================================
20
+
21
+ def prepare_tiny_imagenet():
22
+ """
23
+ Downloads and extracts Tiny-ImageNet if not already present.
24
+
25
+ Returns:
26
+ train_dir, val_dir
27
+ """
28
+
29
+ dataset_url = "http://cs231n.stanford.edu/tiny-imagenet-200.zip"
30
+
31
+ zip_path = "./tiny-imagenet-200.zip"
32
+
33
+ extract_path = "./tiny-imagenet-200"
34
+
35
+ # -----------------------------------------------------
36
+ # Download dataset archive
37
+ # -----------------------------------------------------
38
+ if not os.path.exists(zip_path):
39
+
40
+ print(
41
+ "Downloading Tiny-ImageNet (~230MB)... "
42
+ "Please wait..."
43
+ )
44
+
45
+ urllib.request.urlretrieve(
46
+ dataset_url,
47
+ zip_path
48
+ )
49
+
50
+ print("Download complete!")
51
+
52
+ # -----------------------------------------------------
53
+ # Extract dataset archive
54
+ # -----------------------------------------------------
55
+ if not os.path.exists(extract_path):
56
+
57
+ print("Extracting dataset...")
58
+
59
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
60
+ zip_ref.extractall("./")
61
+
62
+ print("Extraction complete!")
63
+
64
+ return (
65
+ os.path.join(extract_path, "train"),
66
+ os.path.join(extract_path, "val")
67
+ )
68
+
69
+
70
+ train_dir, val_dir = prepare_tiny_imagenet()
71
+
72
+
73
+ # =========================================================
74
+ # 2. VALIDATION FOLDER RESTRUCTURING
75
+ # =========================================================
76
+ #
77
+ # Tiny-ImageNet validation images are originally placed
78
+ # in a single shared folder.
79
+ #
80
+ # This section reorganizes them into class-specific
81
+ # folders so torchvision.datasets.ImageFolder can
82
+ # load them correctly.
83
+ #
84
+
85
+ val_img_dir = "./tiny-imagenet-200/val/images"
86
+
87
+ val_annotations = (
88
+ "./tiny-imagenet-200/val/val_annotations.txt"
89
+ )
90
+
91
+ if os.path.exists(val_img_dir):
92
+
93
+ print(
94
+ "Reorganizing Tiny-ImageNet validation "
95
+ "folder structure..."
96
+ )
97
+
98
+ with open(val_annotations, "r") as f:
99
+ lines = f.readlines()
100
+
101
+ for line in lines:
102
+
103
+ parts = line.strip().split("\t")
104
+
105
+ img_name = parts[0]
106
+ class_name = parts[1]
107
+
108
+ class_dir = os.path.join(
109
+ "./tiny-imagenet-200/val",
110
+ class_name
111
+ )
112
+
113
+ os.makedirs(class_dir, exist_ok=True)
114
+
115
+ src_path = os.path.join(
116
+ val_img_dir,
117
+ img_name
118
+ )
119
+
120
+ dst_path = os.path.join(
121
+ class_dir,
122
+ img_name
123
+ )
124
+
125
+ if os.path.exists(src_path):
126
+ os.rename(src_path, dst_path)
127
+
128
+ os.rmdir(val_img_dir)
129
+
130
+ print(
131
+ "Validation folder restructuring complete!"
132
+ )
133
+
134
+
135
+ # =========================================================
136
+ # 3. DATA AUGMENTATION + NORMALIZATION
137
+ # =========================================================
138
+
139
+ transform_train = transforms.Compose([
140
+
141
+ # Horizontal augmentation
142
+ transforms.RandomHorizontalFlip(),
143
+
144
+ # Mild rotational augmentation
145
+ transforms.RandomRotation(15),
146
+
147
+ transforms.ToTensor(),
148
+
149
+ # Tiny-ImageNet normalization statistics
150
+ transforms.Normalize(
151
+ (0.4802, 0.4481, 0.3975),
152
+ (0.2302, 0.2265, 0.2262)
153
+ )
154
+ ])
155
+
156
+ transform_val = transforms.Compose([
157
+
158
+ transforms.ToTensor(),
159
+
160
+ transforms.Normalize(
161
+ (0.4802, 0.4481, 0.3975),
162
+ (0.2302, 0.2265, 0.2262)
163
+ )
164
+ ])
165
+
166
+
167
+ # =========================================================
168
+ # 4. DATASET + DATALOADER SETUP
169
+ # =========================================================
170
+
171
+ train_dataset = datasets.ImageFolder(
172
+ root=train_dir,
173
+ transform=transform_train
174
+ )
175
+
176
+ val_dataset = datasets.ImageFolder(
177
+ root=val_dir,
178
+ transform=transform_val
179
+ )
180
+
181
+ train_loader = DataLoader(
182
+ train_dataset,
183
+ batch_size=128,
184
+ shuffle=True,
185
+ num_workers=2,
186
+ pin_memory=True
187
+ )
188
+
189
+ val_loader = DataLoader(
190
+ val_dataset,
191
+ batch_size=256,
192
+ shuffle=False,
193
+ num_workers=2,
194
+ pin_memory=True
195
+ )
196
+
197
+
198
+ # =========================================================
199
+ # 5. CORE RELATIONAL LAYER — LOOKTHEM LAYER
200
+ # =========================================================
201
+
202
+ class LookThemLayer(nn.Module):
203
+ """
204
+ Token-relational processing layer.
205
+
206
+ Each token owns two independent micro-networks
207
+ whose outputs are compared against every other
208
+ token using ratio-based relational interactions.
209
+
210
+ The interaction maps are transformed and then
211
+ redistributed back into token-space.
212
+ """
213
+
214
+ def __init__(self, num_tokens, in_features, hidden_dim):
215
+
216
+ super(LookThemLayer, self).__init__()
217
+
218
+ self.num_tokens = num_tokens
219
+ self.in_features = in_features
220
+
221
+ # =================================================
222
+ # BRANCH 1 PARAMETERS
223
+ # =================================================
224
+ self.mod1_w1 = nn.Parameter(
225
+ torch.randn(
226
+ num_tokens,
227
+ in_features,
228
+ hidden_dim
229
+ )
230
+ )
231
+
232
+ self.mod1_b1 = nn.Parameter(
233
+ torch.zeros(num_tokens, hidden_dim)
234
+ )
235
+
236
+ self.mod1_w2 = nn.Parameter(
237
+ torch.randn(
238
+ num_tokens,
239
+ hidden_dim,
240
+ 1
241
+ )
242
+ )
243
+
244
+ self.mod1_b2 = nn.Parameter(
245
+ torch.zeros(num_tokens, 1)
246
+ )
247
+
248
+ # =================================================
249
+ # BRANCH 2 PARAMETERS
250
+ # =================================================
251
+ self.mod2_w1 = nn.Parameter(
252
+ torch.randn(
253
+ num_tokens,
254
+ in_features,
255
+ hidden_dim
256
+ )
257
+ )
258
+
259
+ self.mod2_b1 = nn.Parameter(
260
+ torch.zeros(num_tokens, hidden_dim)
261
+ )
262
+
263
+ self.mod2_w2 = nn.Parameter(
264
+ torch.randn(
265
+ num_tokens,
266
+ hidden_dim,
267
+ 1
268
+ )
269
+ )
270
+
271
+ self.mod2_b2 = nn.Parameter(
272
+ torch.zeros(num_tokens, 1)
273
+ )
274
+
275
+ # =================================================
276
+ # RELATIONAL TRANSFORMATION PARAMETERS
277
+ # =================================================
278
+ self.trans_w = nn.Parameter(
279
+ torch.randn(num_tokens, 1, 1)
280
+ )
281
+
282
+ self.trans_b = nn.Parameter(
283
+ torch.zeros(num_tokens, 1)
284
+ )
285
+
286
+ self._init_weights()
287
+
288
+ def _init_weights(self):
289
+ """
290
+ Kaiming initialization for all learnable
291
+ projection matrices.
292
+ """
293
+
294
+ for w in [
295
+ self.mod1_w1,
296
+ self.mod2_w1,
297
+ self.mod1_w2,
298
+ self.mod2_w2,
299
+ self.trans_w
300
+ ]:
301
+ nn.init.kaiming_uniform_(
302
+ w,
303
+ a=math.sqrt(5)
304
+ )
305
+
306
+ def forward(self, x):
307
+ """
308
+ Input shape:
309
+ [B, Tokens, Features]
310
+
311
+ Output shape:
312
+ [B, Tokens, Features]
313
+ """
314
+
315
+ N = self.num_tokens
316
+
317
+ # =================================================
318
+ # BRANCH 1 FORWARD PASS
319
+ # =================================================
320
+ h1 = (
321
+ torch.einsum(
322
+ 'bti,tij->btj',
323
+ x,
324
+ self.mod1_w1
325
+ )
326
+ + self.mod1_b1
327
+ )
328
+
329
+ out_m1 = (
330
+ torch.einsum(
331
+ 'btj,tjk->btk',
332
+ F.gelu(h1),
333
+ self.mod1_w2
334
+ )
335
+ + self.mod1_b2
336
+ )
337
+
338
+ # =================================================
339
+ # BRANCH 2 FORWARD PASS
340
+ # =================================================
341
+ h2 = (
342
+ torch.einsum(
343
+ 'bti,tij->btj',
344
+ x,
345
+ self.mod2_w1
346
+ )
347
+ + self.mod2_b1
348
+ )
349
+
350
+ out_m2 = (
351
+ torch.einsum(
352
+ 'btj,tjk->btk',
353
+ F.gelu(h2),
354
+ self.mod2_w2
355
+ )
356
+ + self.mod2_b2
357
+ )
358
+
359
+ # Numerical stabilization
360
+ out_m2_safe = out_m2 + 1e-5
361
+
362
+ # =================================================
363
+ # PAIRWISE TOKEN RELATIONAL COMPARISON
364
+ # =================================================
365
+
366
+ compare = torch.tanh(
367
+ out_m1.unsqueeze(2) /
368
+ out_m2_safe.unsqueeze(1)
369
+ )
370
+
371
+ compare2 = torch.tanh(
372
+ out_m1.unsqueeze(1) /
373
+ out_m2_safe.unsqueeze(2)
374
+ )
375
+
376
+ # =================================================
377
+ # RELATIONAL MAP TRANSFORMATION
378
+ # =================================================
379
+ bias_reshaped = self.trans_b.view(
380
+ 1,
381
+ 1,
382
+ N,
383
+ 1
384
+ )
385
+
386
+ trans_compare = (
387
+ torch.einsum(
388
+ 'bije,jef->bijf',
389
+ compare,
390
+ self.trans_w
391
+ )
392
+ + bias_reshaped
393
+ )
394
+
395
+ trans_compare2 = (
396
+ torch.einsum(
397
+ 'bije,jef->bijf',
398
+ compare2,
399
+ self.trans_w
400
+ )
401
+ + bias_reshaped
402
+ )
403
+
404
+ # =================================================
405
+ # BIDIRECTIONAL INTERACTION FUSION
406
+ # =================================================
407
+ interaction = (
408
+ trans_compare * x.unsqueeze(2)
409
+ + trans_compare2 * x.unsqueeze(1)
410
+ ) / 2
411
+
412
+ # Remove self-interaction
413
+ mask = 1.0 - torch.eye(
414
+ N,
415
+ device=x.device
416
+ )
417
+
418
+ interaction_masked = (
419
+ interaction *
420
+ mask.view(1, N, N, 1)
421
+ )
422
+
423
+ # Aggregate external token interactions
424
+ return (
425
+ interaction_masked.sum(dim=2)
426
+ / (N - 1.0)
427
+ )
428
+
429
+
430
+ # =========================================================
431
+ # 6. MAIN ARCHITECTURE — LOOKTHEM V5
432
+ # =========================================================
433
+
434
+ class LookThemV5(nn.Module):
435
+ """
436
+ Dual-stream asymmetric relational architecture.
437
+
438
+ Stream A:
439
+ High-resolution grayscale macro-structure stream.
440
+
441
+ Stream B:
442
+ RGB color-essence stream compressed into
443
+ lower spatial resolution.
444
+
445
+ Both streams are fused at feature-level and
446
+ processed through the relational LookThem core.
447
+ """
448
+
449
+ def __init__(self):
450
+
451
+ super(LookThemV5, self).__init__()
452
+
453
+ # =================================================
454
+ # RGB → GRAYSCALE CONVERSION WEIGHTS
455
+ # =================================================
456
+ self.register_buffer(
457
+ 'grayscale_weights',
458
+ torch.tensor(
459
+ [0.299, 0.587, 0.114]
460
+ ).view(1, 3, 1, 1)
461
+ )
462
+
463
+ # =================================================
464
+ # STREAM A — MACRO STRUCTURE STREAM
465
+ # =================================================
466
+ #
467
+ # Preserves higher spatial resolution (16x16)
468
+ # to retain broader structural information.
469
+ #
470
+ self.stream_a = nn.Sequential(
471
+
472
+ nn.Conv2d(
473
+ 1,
474
+ 16,
475
+ kernel_size=3,
476
+ stride=2,
477
+ padding=1
478
+ ),
479
+ nn.BatchNorm2d(16),
480
+ nn.GELU(),
481
+
482
+ nn.Conv2d(
483
+ 16,
484
+ 32,
485
+ kernel_size=3,
486
+ stride=2,
487
+ padding=1
488
+ ),
489
+ nn.BatchNorm2d(32),
490
+ nn.GELU()
491
+ )
492
+
493
+ # =================================================
494
+ # TOKEN BRIDGE
495
+ # =================================================
496
+ #
497
+ # Compresses spatial dimension:
498
+ #
499
+ # 256 spatial positions → 64 tokens
500
+ #
501
+ # while preserving feature channels.
502
+ #
503
+ self.token_bridge = nn.Linear(256, 64)
504
+
505
+ # =================================================
506
+ # STREAM B — COLOR ESSENCE STREAM
507
+ # =================================================
508
+ #
509
+ # RGB stream reduced into 8x8 spatial layout
510
+ # using pure stride-based standard convolutions.
511
+ #
512
+ self.stream_b = nn.Sequential(
513
+
514
+ nn.Conv2d(
515
+ 3,
516
+ 16,
517
+ kernel_size=3,
518
+ stride=2,
519
+ padding=1
520
+ ),
521
+ nn.BatchNorm2d(16),
522
+ nn.GELU(),
523
+
524
+ nn.Conv2d(
525
+ 16,
526
+ 32,
527
+ kernel_size=3,
528
+ stride=2,
529
+ padding=1
530
+ ),
531
+ nn.BatchNorm2d(32),
532
+ nn.GELU(),
533
+
534
+ nn.Conv2d(
535
+ 32,
536
+ 32,
537
+ kernel_size=3,
538
+ stride=2,
539
+ padding=1
540
+ ),
541
+ nn.BatchNorm2d(32),
542
+ nn.GELU()
543
+ )
544
+
545
+ # =================================================
546
+ # RELATIONAL COGNITION CORE
547
+ # =================================================
548
+ self.lookthem = LookThemLayer(
549
+ num_tokens=64,
550
+ in_features=64,
551
+ hidden_dim=32
552
+ )
553
+
554
+ # =================================================
555
+ # CLASSIFIER HEAD
556
+ # =================================================
557
+ #
558
+ # Flattened relational token representation
559
+ # followed by lightweight anti-overfit head.
560
+ #
561
+ self.classifier = nn.Sequential(
562
+
563
+ nn.Flatten(),
564
+
565
+ nn.Linear(64 * 64, 256),
566
+
567
+ nn.ReLU(),
568
+
569
+ nn.Dropout(0.4),
570
+
571
+ nn.Linear(256, 200)
572
+ )
573
+
574
+ def forward(self, x):
575
+
576
+ batch_size = x.size(0)
577
+
578
+ # =================================================
579
+ # STREAM A — GRAYSCALE MACRO EXTRACTION
580
+ # =================================================
581
+
582
+ # Convert RGB image into grayscale
583
+ x_gray = torch.sum(
584
+ x * self.grayscale_weights,
585
+ dim=1,
586
+ keepdim=True
587
+ )
588
+
589
+ feat_a = self.stream_a(x_gray)
590
+
591
+ # Shape:
592
+ # [B, 32, 16, 16]
593
+
594
+ feat_a_flat = feat_a.view(
595
+ batch_size,
596
+ 32,
597
+ 256
598
+ )
599
+
600
+ # Spatial compression:
601
+ # 256 → 64 tokens
602
+ feat_a_compressed = self.token_bridge(
603
+ feat_a_flat
604
+ )
605
+
606
+ feat_a_tokens = (
607
+ feat_a_compressed.transpose(1, 2)
608
+ )
609
+
610
+ # Final shape:
611
+ # [B, 64 Tokens, 32 Features]
612
+
613
+ # =================================================
614
+ # STREAM B — RGB COLOR EXTRACTION
615
+ # =================================================
616
+
617
+ feat_b = self.stream_b(x)
618
+
619
+ feat_b_tokens = (
620
+ feat_b
621
+ .view(batch_size, 32, 64)
622
+ .transpose(1, 2)
623
+ )
624
+
625
+ # Final shape:
626
+ # [B, 64 Tokens, 32 Features]
627
+
628
+ # =================================================
629
+ # ASYMMETRIC FEATURE FUSION
630
+ # =================================================
631
+ #
632
+ # Token count remains fixed while
633
+ # feature dimensionality is doubled.
634
+ #
635
+ tokens_combined = torch.cat(
636
+ [feat_a_tokens, feat_b_tokens],
637
+ dim=2
638
+ )
639
+
640
+ # Final shape:
641
+ # [B, 64 Tokens, 64 Features]
642
+
643
+ # =================================================
644
+ # RELATIONAL COGNITION
645
+ # =================================================
646
+ out_lookthem = self.lookthem(
647
+ tokens_combined
648
+ )
649
+
650
+ # =================================================
651
+ # CLASSIFICATION
652
+ # =================================================
653
+ return self.classifier(out_lookthem)
654
+
655
+
656
+ # =========================================================
657
+ # 7. TRAINING RUNTIME + CHECKPOINT SYSTEM
658
+ # =========================================================
659
+
660
+ device = torch.device(
661
+ "cuda" if torch.cuda.is_available() else "cpu"
662
+ )
663
+
664
+ model = LookThemV5().to(device)
665
+
666
+ criterion = nn.CrossEntropyLoss()
667
+
668
+ optimizer = optim.Adam(
669
+ model.parameters(),
670
+ lr=0.001,
671
+ weight_decay=1e-4
672
+ )
673
+
674
+ scheduler = optim.lr_scheduler.CosineAnnealingLR(
675
+ optimizer,
676
+ T_max=20
677
+ )
678
+
679
+ start_epoch = 0
680
+
681
+ checkpoint_path = "lookthem_v5_checkpoint.pth"
682
+
683
+
684
+ # =========================================================
685
+ # CHECKPOINT RESUME
686
+ # =========================================================
687
+
688
+ if os.path.exists(checkpoint_path):
689
+
690
+ print(
691
+ "Checkpoint detected. "
692
+ "Resuming previous experiment..."
693
+ )
694
+
695
+ checkpoint = torch.load(checkpoint_path)
696
+
697
+ model.load_state_dict(
698
+ checkpoint['model_state_dict']
699
+ )
700
+
701
+ optimizer.load_state_dict(
702
+ checkpoint['optimizer_state_dict']
703
+ )
704
+
705
+ scheduler.load_state_dict(
706
+ checkpoint['scheduler_state_dict']
707
+ )
708
+
709
+ start_epoch = checkpoint['epoch']
710
+
711
+ print(
712
+ f"Successfully resumed from "
713
+ f"epoch {start_epoch + 1}"
714
+ )
715
+
716
+
717
+ print(
718
+ f"Starting LookThem V5 "
719
+ f"(Asymmetric Fusion) on {device}..."
720
+ )
721
+
722
+
723
+ # =========================================================
724
+ # 8. TRAINING LOOP
725
+ # =========================================================
726
+
727
+ for epoch in range(start_epoch, 20):
728
+
729
+ model.train()
730
+
731
+ total_loss = 0
732
+ correct = 0
733
+ total = 0
734
+
735
+ for data, target in train_loader:
736
+
737
+ data = data.to(device)
738
+
739
+ target = target.to(device)
740
+
741
+ optimizer.zero_grad()
742
+
743
+ output = model(data)
744
+
745
+ loss = criterion(output, target)
746
+
747
+ loss.backward()
748
+
749
+ optimizer.step()
750
+
751
+ total_loss += loss.item()
752
+
753
+ _, predicted = output.max(1)
754
+
755
+ total += target.size(0)
756
+
757
+ correct += predicted.eq(target).sum().item()
758
+
759
+ scheduler.step()
760
+
761
+ acc = 100. * correct / total
762
+
763
+ current_lr = optimizer.param_groups[0]['lr']
764
+
765
+ print(
766
+ f"Epoch {epoch+1:02d}/20 | "
767
+ f"Train Loss: "
768
+ f"{total_loss / len(train_loader):.4f} | "
769
+ f"Train Acc: {acc:.2f}% | "
770
+ f"LR: {current_lr:.6f}"
771
+ )
772
+
773
+ # -----------------------------------------------------
774
+ # Periodic checkpoint save
775
+ # -----------------------------------------------------
776
+ if (epoch + 1) % 5 == 0:
777
+
778
+ torch.save({
779
+
780
+ 'epoch': epoch + 1,
781
+
782
+ 'model_state_dict':
783
+ model.state_dict(),
784
+
785
+ 'optimizer_state_dict':
786
+ optimizer.state_dict(),
787
+
788
+ 'scheduler_state_dict':
789
+ scheduler.state_dict(),
790
+
791
+ }, checkpoint_path)
792
+
793
+ print(
794
+ f"[CHECKPOINT] "
795
+ f"Epoch {epoch+1} saved successfully."
796
+ )
797
+
798
+
799
+ # =========================================================
800
+ # 9. FINAL VALIDATION
801
+ # =========================================================
802
+
803
+ model.eval()
804
+
805
+ test_loss = 0
806
+ test_correct = 0
807
+ test_total = 0
808
+
809
+ print("\nStarting final validation...")
810
+
811
+ with torch.no_grad():
812
+
813
+ for data, target in val_loader:
814
+
815
+ data = data.to(device)
816
+
817
+ target = target.to(device)
818
+
819
+ output = model(data)
820
+
821
+ loss = criterion(output, target)
822
+
823
+ test_loss += loss.item()
824
+
825
+ _, predicted = output.max(1)
826
+
827
+ test_total += target.size(0)
828
+
829
+ test_correct += predicted.eq(target).sum().item()
830
+
831
+ final_test_acc = (
832
+ 100. * test_correct / test_total
833
+ )
834
+
835
+ print("=== FINAL LOOKTHEM V5 RESULTS ===")
836
+
837
+ print(
838
+ f"Test Loss: "
839
+ f"{test_loss / len(val_loader):.4f} | "
840
+ f"Test Accuracy: {final_test_acc:.2f}%"
841
+ )
842
+
843
+ # Save final trained weights
844
+ torch.save(
845
+ model.state_dict(),
846
+ "LookThem_V5_Final.pth"
847
+ )
848
+
849
+ print(
850
+ f"Training complete! "
851
+ f"Final model size: "
852
+ f"{os.path.getsize('LookThem_V5_Final.pth') / (1024*1024):.2f} MB"
853
+ )
854
+ ```