aboutsummaryrefslogtreecommitdiff
path: root/insane-shortest-paths.ipynb
blob: a3e58fb2a1644c1ea1989c8d1e279abbaa3c05d5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "execution_state": "idle",
   "id": "86ce5f44-94f6-43b0-a0d1-091b8134ffb6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total number of parameters: 44352\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import random\n",
    "from collections import deque\n",
    "\n",
    "# Set manual seeds for reproducibility\n",
    "# torch.manual_seed(33)\n",
    "# random.seed(33)\n",
    "\n",
    "# Configuration\n",
    "NVTXS = 16\n",
    "MAXDIST = NVTXS + 1\n",
    "AVGDEG = 2\n",
    "SEQLEN = NVTXS + 1\n",
    "HIDDENDIM = 4 * NVTXS + 2\n",
    "\n",
    "# Start indices for different sections of the input data\n",
    "START_REACH = NVTXS + 1\n",
    "START_OUT = 2 * NVTXS + 1\n",
    "START_SELF = 3 * NVTXS + 1\n",
    "SRC_FLAG_IDX = START_SELF\n",
    "ANS_FLAG_IDX = 0\n",
    "NOTANS_FLAG_IDX = -1\n",
    "\n",
    "# Determine device\n",
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "\n",
    "def random_graph(device):\n",
    "    \"\"\"Generate a random graph tensor.\"\"\"\n",
    "    data = torch.zeros((SEQLEN, HIDDENDIM), device=device)\n",
    "    \n",
    "    # Mark self vertices\n",
    "    for i in range(1, NVTXS + 1):\n",
    "        data[i, START_SELF - 1 + i] = 1\n",
    "\n",
    "    # Create adjacency list\n",
    "    adj_list = [set() for _ in range(SEQLEN)]\n",
    "    indices = [random.randint(1, NVTXS) for _ in range(AVGDEG * NVTXS)]\n",
    "    \n",
    "    for i in range(0, len(indices), 2):\n",
    "        u = indices[i]\n",
    "        v = indices[i + 1]\n",
    "        if u != v:\n",
    "            # Bidirectional connections\n",
    "            data[v, u] = 1\n",
    "            data[u, v] = 1\n",
    "            data[v, NVTXS + u] = 1\n",
    "            data[u, NVTXS + v] = 1\n",
    "            adj_list[u].add(v)\n",
    "            adj_list[v].add(u)\n",
    "\n",
    "    # Set flags\n",
    "    data[0, ANS_FLAG_IDX] = 1\n",
    "    data[1:, NOTANS_FLAG_IDX] = 1\n",
    "    data[0, START_REACH:START_REACH + NVTXS] = 1\n",
    "    return data, adj_list\n",
    "\n",
    "def SSSP(G):\n",
    "    \"\"\"Single Source Shortest Path algorithm.\"\"\"\n",
    "    dist = [MAXDIST for _ in G]\n",
    "    dist[1] = 0\n",
    "    frontier = deque([1])\n",
    "    while frontier:\n",
    "        vtx = frontier.popleft()\n",
    "        for x in G[vtx]:\n",
    "            if dist[x] == MAXDIST:\n",
    "                dist[x] = 1 + dist[vtx]\n",
    "                frontier.append(x)\n",
    "                if x == 2:\n",
    "                    return dist[2]\n",
    "    return MAXDIST\n",
    "\n",
    "def mkbatch(size):\n",
    "    \"\"\"Create a batch of graph data.\"\"\"\n",
    "    graphs = []\n",
    "    distances = []\n",
    "\n",
    "    for _ in range(size):\n",
    "        data, adj_list = random_graph(device)\n",
    "        dist = SSSP(adj_list)\n",
    "        graphs.append(data)\n",
    "        distances.append(dist)\n",
    "\n",
    "    data = torch.stack(graphs)\n",
    "    labels = torch.tensor(distances, dtype=torch.float32, device=device)\n",
    "    return data, labels\n",
    "\n",
    "BIG = 20\n",
    "SUPABIG = 100\n",
    "MED = 10\n",
    "CURSE = 5\n",
    "\n",
    "class SillyTransformer(nn.Module):\n",
    "    def __init__(self, device):\n",
    "        super().__init__()\n",
    "        self.device = device\n",
    "\n",
    "        with torch.no_grad():\n",
    "            # Initialize weight parameters with specific configurations\n",
    "            self.mostKs = nn.ParameterList()\n",
    "            self.mostQs = nn.ParameterList()\n",
    "            self.mostVs = nn.ParameterList()\n",
    "            for head in range(1, NVTXS + 1):\n",
    "                Q = nn.Parameter(torch.zeros((2, HIDDENDIM), device=device))\n",
    "                Q[0, START_REACH - 1 + head] = SUPABIG\n",
    "                Q[1, NOTANS_FLAG_IDX] = 1\n",
    "                K = nn.Parameter(torch.zeros((2, HIDDENDIM), device=device))\n",
    "                K[0, head] = 1\n",
    "                K[1, ANS_FLAG_IDX] = BIG\n",
    "\n",
    "                V = nn.Parameter(torch.zeros((NVTXS, HIDDENDIM), device=device))\n",
    "                for i in range(NVTXS):\n",
    "                    V[i, START_SELF + i] = 1\n",
    "\n",
    "                self.mostKs.append(K)\n",
    "                self.mostQs.append(Q)\n",
    "                self.mostVs.append(V)\n",
    "            self.weirdKs = nn.ParameterList()\n",
    "            self.weirdQs = nn.ParameterList()\n",
    "            self.weirdVs = nn.ParameterList()\n",
    "            for layer in range(NVTXS):\n",
    "                K = nn.Parameter(torch.zeros((3, HIDDENDIM), device=device))\n",
    "                K[0, NOTANS_FLAG_IDX] = -BIG\n",
    "                K[0, SRC_FLAG_IDX] = BIG+SUPABIG\n",
    "                K[1, NOTANS_FLAG_IDX] = -SUPABIG\n",
    "                K[1, NVTXS + 2] = BIG+SUPABIG\n",
    "                K[1, ANS_FLAG_IDX] = -BIG-SUPABIG\n",
    "                K[2, ANS_FLAG_IDX] = MED\n",
    "\n",
    "                Q = nn.Parameter(torch.zeros((3, HIDDENDIM), device=device))\n",
    "                Q[:, ANS_FLAG_IDX] = 1\n",
    "\n",
    "                V = nn.Parameter(torch.zeros((NVTXS, HIDDENDIM), device=device))\n",
    "                V[layer, SRC_FLAG_IDX] = 1\n",
    "\n",
    "                self.weirdKs.append(K)\n",
    "                self.weirdQs.append(Q)\n",
    "                self.weirdVs.append(V)\n",
    "\n",
    "    def forward(self, src):\n",
    "        for layer in range(NVTXS):\n",
    "            allKs = [self.weirdKs[layer]] + [x for x in self.mostKs]\n",
    "            allQs = [self.weirdQs[layer]] + [x for x in self.mostQs]\n",
    "            allVs = [self.weirdVs[layer]] + [x for x in self.mostVs]\n",
    "            head_outputs = []\n",
    "            \n",
    "            for (K, Q, V) in zip(allKs, allQs, allVs):\n",
    "                ksrc = torch.matmul(src, K.unsqueeze(0).transpose(-2, -1))\n",
    "                qsrc = torch.matmul(src, Q.unsqueeze(0).transpose(-2, -1))\n",
    "                vsrc = torch.matmul(src, V.unsqueeze(0).transpose(-2, -1))\n",
    "\n",
    "                scores = torch.matmul(qsrc, ksrc.transpose(-2, -1))\n",
    "                attention_weights = torch.softmax(scores, dim=-1)\n",
    "                head_output = torch.matmul(attention_weights, vsrc)\n",
    "                head_outputs.append(head_output)\n",
    "\n",
    "            new_reaches = sum(head_outputs[1:])\n",
    "            BSZ = new_reaches.shape[0]\n",
    "\n",
    "            nodelta_nbrs = torch.zeros((BSZ, SEQLEN, NVTXS + 1), device=self.device)\n",
    "            morepadlol = torch.zeros((BSZ, SEQLEN, 1 + NVTXS), device=self.device)\n",
    "\n",
    "            src = src + torch.cat((nodelta_nbrs, new_reaches, head_outputs[0], morepadlol), dim=2)\n",
    "            src[:, :, START_REACH:START_REACH + NVTXS] = 2 * torch.sigmoid(src[:, :, START_REACH:START_REACH + NVTXS] * CURSE) - 1\n",
    "\n",
    "        canreach = src[:, 0, START_OUT:START_OUT + NVTXS]\n",
    "        final_output = 1 + torch.sum(1 - canreach, dim=1)\n",
    "        return final_output\n",
    "\n",
    "model = SillyTransformer(device).to(device)\n",
    "params = sum(p.numel() for p in model.parameters())\n",
    "print(f\"Total number of parameters: {params}\")\n",
    "\n",
    "def destroy_rand_weights(model):\n",
    "    weight_lists = [model.mostKs, model.mostQs, model.mostVs, \n",
    "                    model.weirdKs, model.weirdQs, model.weirdVs]\n",
    "    random_list = random.choice(weight_lists)\n",
    "    random_matrix = random.choice(random_list)\n",
    "    random_matrix.data = torch.randn_like(random_matrix)\n",
    "\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=3e-5)\n",
    "loss_fn = nn.MSELoss()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "execution_state": "idle",
   "id": "a9dd76f4-96f2-47b5-9bb9-a32a1b478dd4",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_15454/381745885.py:148: UserWarning: Attempting to use hipBLASLt on an unsupported architecture! Overriding blas backend to hipblas (Triggered internally at ../aten/src/ATen/Context.cpp:296.)\n",
      "  ksrc = torch.matmul(src, K.unsqueeze(0).transpose(-2, -1))\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch [0/10000], Loss: 0.0025\n",
      "Epoch [10/10000], Loss: 6.4609\n",
      "Epoch [20/10000], Loss: 11.0729\n",
      "Epoch [30/10000], Loss: 10.3862\n",
      "Epoch [40/10000], Loss: 8.3659\n",
      "Epoch [50/10000], Loss: 8.4364\n",
      "Epoch [60/10000], Loss: 7.0110\n",
      "Epoch [70/10000], Loss: 6.2279\n",
      "Epoch [80/10000], Loss: 14.5876\n",
      "Epoch [90/10000], Loss: 13.5753\n",
      "Epoch [100/10000], Loss: 15.5835\n",
      "Epoch [110/10000], Loss: 14.3249\n",
      "Epoch [120/10000], Loss: 11.1069\n",
      "Epoch [130/10000], Loss: 11.6783\n",
      "Epoch [140/10000], Loss: 10.2477\n",
      "Epoch [150/10000], Loss: 10.8494\n",
      "Epoch [160/10000], Loss: 8.3007\n",
      "Epoch [170/10000], Loss: 6.8133\n",
      "Epoch [180/10000], Loss: 5.5992\n",
      "Epoch [190/10000], Loss: 6.9212\n",
      "Epoch [200/10000], Loss: 5.9311\n",
      "Epoch [210/10000], Loss: 6.0747\n",
      "Epoch [220/10000], Loss: 4.9251\n",
      "Epoch [230/10000], Loss: 3.9548\n",
      "Epoch [240/10000], Loss: 5.9888\n",
      "Epoch [250/10000], Loss: 4.9153\n",
      "Epoch [260/10000], Loss: 6.6282\n",
      "Epoch [270/10000], Loss: 4.7945\n",
      "Epoch [280/10000], Loss: 6.8866\n",
      "Epoch [290/10000], Loss: 5.7963\n",
      "Epoch [300/10000], Loss: 4.1406\n",
      "Epoch [310/10000], Loss: 5.8112\n",
      "Epoch [320/10000], Loss: 6.3739\n",
      "Epoch [330/10000], Loss: 4.7297\n",
      "Epoch [340/10000], Loss: 3.6125\n",
      "Epoch [350/10000], Loss: 4.7553\n",
      "Epoch [360/10000], Loss: 5.1536\n",
      "Epoch [370/10000], Loss: 3.3294\n",
      "Epoch [380/10000], Loss: 4.8955\n",
      "Epoch [390/10000], Loss: 5.0702\n",
      "Epoch [400/10000], Loss: 5.5217\n",
      "Epoch [410/10000], Loss: 4.0543\n",
      "Epoch [420/10000], Loss: 3.8583\n",
      "Epoch [430/10000], Loss: 4.4484\n",
      "Epoch [440/10000], Loss: 6.0914\n",
      "Epoch [450/10000], Loss: 5.3544\n",
      "Epoch [460/10000], Loss: 3.1850\n",
      "Epoch [470/10000], Loss: 4.5308\n",
      "Epoch [480/10000], Loss: 3.6213\n",
      "Epoch [490/10000], Loss: 3.3625\n",
      "Epoch [500/10000], Loss: 3.4060\n",
      "Epoch [510/10000], Loss: 3.2437\n",
      "Epoch [520/10000], Loss: 3.9425\n",
      "Epoch [530/10000], Loss: 3.4496\n",
      "Epoch [540/10000], Loss: 2.8899\n",
      "Epoch [550/10000], Loss: 2.5607\n",
      "Epoch [560/10000], Loss: 3.9549\n",
      "Epoch [570/10000], Loss: 4.4588\n",
      "Epoch [580/10000], Loss: 4.3738\n",
      "Epoch [590/10000], Loss: 3.3019\n",
      "Epoch [600/10000], Loss: 2.4798\n",
      "Epoch [610/10000], Loss: 6.1956\n",
      "Epoch [620/10000], Loss: 4.3365\n",
      "Epoch [630/10000], Loss: 4.9766\n",
      "Epoch [640/10000], Loss: 4.2719\n",
      "Epoch [650/10000], Loss: 5.0380\n",
      "Epoch [660/10000], Loss: 5.7970\n",
      "Epoch [670/10000], Loss: 5.8626\n",
      "Epoch [680/10000], Loss: 4.7593\n",
      "Epoch [690/10000], Loss: 5.7902\n",
      "Epoch [700/10000], Loss: 5.9829\n",
      "Epoch [710/10000], Loss: 6.7365\n",
      "Epoch [720/10000], Loss: 5.3005\n",
      "Epoch [730/10000], Loss: 5.8437\n",
      "Epoch [740/10000], Loss: 3.8711\n",
      "Epoch [750/10000], Loss: 5.0535\n",
      "Epoch [760/10000], Loss: 3.7943\n",
      "Epoch [770/10000], Loss: 4.5757\n",
      "Epoch [780/10000], Loss: 4.7215\n",
      "Epoch [790/10000], Loss: 4.4653\n",
      "Epoch [800/10000], Loss: 5.5769\n",
      "Epoch [810/10000], Loss: 5.0396\n",
      "Epoch [820/10000], Loss: 5.2878\n",
      "Epoch [830/10000], Loss: 5.4599\n",
      "Epoch [840/10000], Loss: 4.5172\n",
      "Epoch [850/10000], Loss: 4.8925\n",
      "Epoch [860/10000], Loss: 5.1588\n",
      "Epoch [870/10000], Loss: 5.8972\n",
      "Epoch [880/10000], Loss: 4.9056\n",
      "Epoch [890/10000], Loss: 4.8735\n",
      "Epoch [900/10000], Loss: 5.2677\n",
      "Epoch [910/10000], Loss: 4.3955\n",
      "Epoch [920/10000], Loss: 5.1297\n",
      "Epoch [930/10000], Loss: 4.2394\n",
      "Epoch [940/10000], Loss: 6.8890\n",
      "Epoch [950/10000], Loss: 5.1845\n",
      "Epoch [960/10000], Loss: 4.4620\n",
      "Epoch [970/10000], Loss: 5.1748\n",
      "Epoch [980/10000], Loss: 4.9878\n",
      "Epoch [990/10000], Loss: 3.9090\n",
      "Epoch [1000/10000], Loss: 3.6580\n",
      "Epoch [1010/10000], Loss: 3.5537\n",
      "Epoch [1020/10000], Loss: 4.5068\n",
      "Epoch [1030/10000], Loss: 3.2602\n",
      "Epoch [1040/10000], Loss: 3.7987\n",
      "Epoch [1050/10000], Loss: 3.3821\n",
      "Epoch [1060/10000], Loss: 3.9663\n",
      "Epoch [1070/10000], Loss: 3.8562\n",
      "Epoch [1080/10000], Loss: 3.7811\n",
      "Epoch [1090/10000], Loss: 4.0012\n",
      "Epoch [1100/10000], Loss: 4.2564\n",
      "Epoch [1110/10000], Loss: 3.2248\n",
      "Epoch [1120/10000], Loss: 3.7483\n",
      "Epoch [1130/10000], Loss: 3.0315\n",
      "Epoch [1140/10000], Loss: 3.3677\n",
      "Epoch [1150/10000], Loss: 3.5367\n",
      "Epoch [1160/10000], Loss: 2.7389\n",
      "Epoch [1170/10000], Loss: 3.0337\n",
      "Epoch [1180/10000], Loss: 2.2630\n",
      "Epoch [1190/10000], Loss: 1.8084\n",
      "Epoch [1200/10000], Loss: 3.3239\n",
      "Epoch [1210/10000], Loss: 4.1555\n",
      "Epoch [1220/10000], Loss: 2.8362\n",
      "Epoch [1230/10000], Loss: 3.1269\n",
      "Epoch [1240/10000], Loss: 2.2517\n",
      "Epoch [1250/10000], Loss: 2.9400\n",
      "Epoch [1260/10000], Loss: 2.5436\n",
      "Epoch [1270/10000], Loss: 2.6504\n",
      "Epoch [1280/10000], Loss: 2.9571\n",
      "Epoch [1290/10000], Loss: 2.4060\n",
      "Epoch [1300/10000], Loss: 2.6461\n",
      "Epoch [1310/10000], Loss: 2.4692\n",
      "Epoch [1320/10000], Loss: 2.0638\n",
      "Epoch [1330/10000], Loss: 3.0852\n",
      "Epoch [1340/10000], Loss: 2.3448\n",
      "Epoch [1350/10000], Loss: 2.6796\n",
      "Epoch [1360/10000], Loss: 2.0310\n",
      "Epoch [1370/10000], Loss: 1.8680\n",
      "Epoch [1380/10000], Loss: 2.1846\n",
      "Epoch [1390/10000], Loss: 2.3017\n",
      "Epoch [1400/10000], Loss: 1.6519\n",
      "Epoch [1410/10000], Loss: 1.6228\n",
      "Epoch [1420/10000], Loss: 1.4328\n",
      "Epoch [1430/10000], Loss: 1.5642\n",
      "Epoch [1440/10000], Loss: 1.8962\n",
      "Epoch [1450/10000], Loss: 1.4433\n",
      "Epoch [1460/10000], Loss: 2.1973\n",
      "Epoch [1470/10000], Loss: 1.8118\n",
      "Epoch [1480/10000], Loss: 1.7155\n",
      "Epoch [1490/10000], Loss: 2.7671\n",
      "Epoch [1500/10000], Loss: 2.4518\n",
      "Epoch [1510/10000], Loss: 1.0202\n",
      "Epoch [1520/10000], Loss: 2.2548\n",
      "Epoch [1530/10000], Loss: 1.4305\n",
      "Epoch [1540/10000], Loss: 2.1796\n",
      "Epoch [1550/10000], Loss: 1.7766\n",
      "Epoch [1560/10000], Loss: 2.0751\n",
      "Epoch [1570/10000], Loss: 1.6544\n",
      "Epoch [1580/10000], Loss: 2.8767\n",
      "Epoch [1590/10000], Loss: 2.2069\n",
      "Epoch [1600/10000], Loss: 1.5974\n",
      "Epoch [1610/10000], Loss: 2.0101\n",
      "Epoch [1620/10000], Loss: 1.9445\n",
      "Epoch [1630/10000], Loss: 2.3080\n",
      "Epoch [1640/10000], Loss: 1.2395\n",
      "Epoch [1650/10000], Loss: 1.2486\n",
      "Epoch [1660/10000], Loss: 1.3250\n",
      "Epoch [1670/10000], Loss: 1.1839\n",
      "Epoch [1680/10000], Loss: 2.0569\n",
      "Epoch [1690/10000], Loss: 2.3591\n",
      "Epoch [1700/10000], Loss: 1.4618\n",
      "Epoch [1710/10000], Loss: 1.3663\n",
      "Epoch [1720/10000], Loss: 0.7300\n",
      "Epoch [1730/10000], Loss: 2.6778\n",
      "Epoch [1740/10000], Loss: 2.2931\n",
      "Epoch [1750/10000], Loss: 1.4289\n",
      "Epoch [1760/10000], Loss: 1.1453\n",
      "Epoch [1770/10000], Loss: 1.0400\n",
      "Epoch [1780/10000], Loss: 1.3603\n",
      "Epoch [1790/10000], Loss: 1.5058\n",
      "Epoch [1800/10000], Loss: 1.2890\n",
      "Epoch [1810/10000], Loss: 1.1259\n",
      "Epoch [1820/10000], Loss: 1.3006\n",
      "Epoch [1830/10000], Loss: 1.4118\n",
      "Epoch [1840/10000], Loss: 1.6406\n",
      "Epoch [1850/10000], Loss: 1.3513\n",
      "Epoch [1860/10000], Loss: 1.2380\n",
      "Epoch [1870/10000], Loss: 1.4618\n",
      "Epoch [1880/10000], Loss: 2.8634\n",
      "Epoch [1890/10000], Loss: 2.4145\n",
      "Epoch [1900/10000], Loss: 2.1412\n",
      "Epoch [1910/10000], Loss: 2.7974\n",
      "Epoch [1920/10000], Loss: 2.3607\n",
      "Epoch [1930/10000], Loss: 2.1780\n",
      "Epoch [1940/10000], Loss: 2.1544\n",
      "Epoch [1950/10000], Loss: 1.1798\n",
      "Epoch [1960/10000], Loss: 2.0259\n",
      "Epoch [1970/10000], Loss: 8.5005\n",
      "Epoch [1980/10000], Loss: 7.2836\n",
      "Epoch [1990/10000], Loss: 5.1658\n",
      "Epoch [2000/10000], Loss: 4.4845\n",
      "Epoch [2010/10000], Loss: 3.2873\n",
      "Epoch [2020/10000], Loss: 3.9213\n",
      "Epoch [2030/10000], Loss: 3.4896\n",
      "Epoch [2040/10000], Loss: 4.8792\n",
      "Epoch [2050/10000], Loss: 3.8883\n",
      "Epoch [2060/10000], Loss: 4.8546\n",
      "Epoch [2070/10000], Loss: 3.5432\n",
      "Epoch [2080/10000], Loss: 4.3267\n",
      "Epoch [2090/10000], Loss: 3.4671\n",
      "Epoch [2100/10000], Loss: 5.4011\n",
      "Epoch [2110/10000], Loss: 5.8443\n",
      "Epoch [2120/10000], Loss: 2.8416\n",
      "Epoch [2130/10000], Loss: 5.1449\n",
      "Epoch [2140/10000], Loss: 5.6858\n",
      "Epoch [2150/10000], Loss: 5.1926\n",
      "Epoch [2160/10000], Loss: 4.5664\n",
      "Epoch [2170/10000], Loss: 4.3358\n",
      "Epoch [2180/10000], Loss: 4.5456\n",
      "Epoch [2190/10000], Loss: 3.5273\n",
      "Epoch [2200/10000], Loss: 6.8660\n",
      "Epoch [2210/10000], Loss: 3.4792\n",
      "Epoch [2220/10000], Loss: 3.4052\n",
      "Epoch [2230/10000], Loss: 2.8651\n",
      "Epoch [2240/10000], Loss: 2.1104\n",
      "Epoch [2250/10000], Loss: 2.1549\n",
      "Epoch [2260/10000], Loss: 1.8513\n",
      "Epoch [2270/10000], Loss: 2.8559\n",
      "Epoch [2280/10000], Loss: 1.3817\n",
      "Epoch [2290/10000], Loss: 1.4976\n",
      "Epoch [2300/10000], Loss: 1.7325\n",
      "Epoch [2310/10000], Loss: 1.5967\n",
      "Epoch [2320/10000], Loss: 0.8749\n",
      "Epoch [2330/10000], Loss: 1.5636\n",
      "Epoch [2340/10000], Loss: 1.5302\n",
      "Epoch [2350/10000], Loss: 0.7900\n",
      "Epoch [2360/10000], Loss: 1.0777\n",
      "Epoch [2370/10000], Loss: 0.6089\n",
      "Epoch [2380/10000], Loss: 1.2180\n",
      "Epoch [2390/10000], Loss: 1.3731\n",
      "Epoch [2400/10000], Loss: 1.1782\n",
      "Epoch [2410/10000], Loss: 0.9826\n",
      "Epoch [2420/10000], Loss: 1.8233\n",
      "Epoch [2430/10000], Loss: 0.8246\n",
      "Epoch [2440/10000], Loss: 0.7204\n",
      "Epoch [2450/10000], Loss: 1.2327\n",
      "Epoch [2460/10000], Loss: 1.2843\n",
      "Epoch [2470/10000], Loss: 1.1326\n",
      "Epoch [2480/10000], Loss: 1.1369\n",
      "Epoch [2490/10000], Loss: 1.0106\n",
      "Epoch [2500/10000], Loss: 1.4563\n",
      "Epoch [2510/10000], Loss: 1.0163\n",
      "Epoch [2520/10000], Loss: 0.3823\n",
      "Epoch [2530/10000], Loss: 0.8872\n",
      "Epoch [2540/10000], Loss: 0.4118\n",
      "Epoch [2550/10000], Loss: 0.4925\n",
      "Epoch [2560/10000], Loss: 0.1141\n",
      "Epoch [2570/10000], Loss: 0.5785\n",
      "Epoch [2580/10000], Loss: 0.5831\n",
      "Epoch [2590/10000], Loss: 0.0845\n",
      "Epoch [2600/10000], Loss: 0.5621\n",
      "Epoch [2610/10000], Loss: 1.0745\n",
      "Epoch [2620/10000], Loss: 0.2378\n",
      "Epoch [2630/10000], Loss: 0.6215\n",
      "Epoch [2640/10000], Loss: 0.7897\n",
      "Epoch [2650/10000], Loss: 0.9359\n",
      "Epoch [2660/10000], Loss: 0.5567\n",
      "Epoch [2670/10000], Loss: 4.0690\n",
      "Epoch [2680/10000], Loss: 3.3254\n",
      "Epoch [2690/10000], Loss: 3.3888\n",
      "Epoch [2700/10000], Loss: 3.7329\n",
      "Epoch [2710/10000], Loss: 2.9879\n",
      "Epoch [2720/10000], Loss: 2.6283\n",
      "Epoch [2730/10000], Loss: 2.4366\n",
      "Epoch [2740/10000], Loss: 2.8078\n",
      "Epoch [2750/10000], Loss: 1.2757\n",
      "Epoch [2760/10000], Loss: 1.0685\n",
      "Epoch [2770/10000], Loss: 2.2750\n",
      "Epoch [2780/10000], Loss: 1.9740\n",
      "Epoch [2790/10000], Loss: 1.4824\n",
      "Epoch [2800/10000], Loss: 1.1974\n",
      "Epoch [2810/10000], Loss: 1.6074\n",
      "Epoch [2820/10000], Loss: 1.2541\n",
      "Epoch [2830/10000], Loss: 1.7665\n",
      "Epoch [2840/10000], Loss: 1.9748\n",
      "Epoch [2850/10000], Loss: 1.9842\n",
      "Epoch [2860/10000], Loss: 2.5544\n",
      "Epoch [2870/10000], Loss: 1.6564\n",
      "Epoch [2880/10000], Loss: 1.0362\n",
      "Epoch [2890/10000], Loss: 1.3166\n",
      "Epoch [2900/10000], Loss: 2.4819\n",
      "Epoch [2910/10000], Loss: 1.1353\n",
      "Epoch [2920/10000], Loss: 1.6106\n",
      "Epoch [2930/10000], Loss: 2.1840\n",
      "Epoch [2940/10000], Loss: 1.4362\n",
      "Epoch [2950/10000], Loss: 0.9568\n",
      "Epoch [2960/10000], Loss: 1.8224\n",
      "Epoch [2970/10000], Loss: 1.2919\n",
      "Epoch [2980/10000], Loss: 1.1351\n",
      "Epoch [2990/10000], Loss: 0.7588\n",
      "Epoch [3000/10000], Loss: 1.2207\n",
      "Epoch [3010/10000], Loss: 1.3446\n",
      "Epoch [3020/10000], Loss: 1.1581\n",
      "Epoch [3030/10000], Loss: 1.0448\n",
      "Epoch [3040/10000], Loss: 2.0898\n",
      "Epoch [3050/10000], Loss: 1.1978\n",
      "Epoch [3060/10000], Loss: 1.2886\n",
      "Epoch [3070/10000], Loss: 1.0066\n",
      "Epoch [3080/10000], Loss: 1.5037\n",
      "Epoch [3090/10000], Loss: 0.6185\n",
      "Epoch [3100/10000], Loss: 0.9835\n",
      "Epoch [3110/10000], Loss: 0.6671\n",
      "Epoch [3120/10000], Loss: 0.6967\n",
      "Epoch [3130/10000], Loss: 1.0013\n",
      "Epoch [3140/10000], Loss: 1.4123\n",
      "Epoch [3150/10000], Loss: 1.8096\n",
      "Epoch [3160/10000], Loss: 1.1270\n",
      "Epoch [3170/10000], Loss: 1.9781\n",
      "Epoch [3180/10000], Loss: 1.0191\n",
      "Epoch [3190/10000], Loss: 2.4403\n",
      "Epoch [3200/10000], Loss: 0.8882\n",
      "Epoch [3210/10000], Loss: 1.0005\n",
      "Epoch [3220/10000], Loss: 0.5002\n",
      "Epoch [3230/10000], Loss: 1.2351\n",
      "Epoch [3240/10000], Loss: 0.8264\n",
      "Epoch [3250/10000], Loss: 0.7881\n",
      "Epoch [3260/10000], Loss: 1.4120\n",
      "Epoch [3270/10000], Loss: 0.3342\n",
      "Epoch [3280/10000], Loss: 1.3266\n",
      "Epoch [3290/10000], Loss: 1.7115\n",
      "Epoch [3300/10000], Loss: 1.0647\n",
      "Epoch [3310/10000], Loss: 0.4623\n",
      "Epoch [3320/10000], Loss: 1.2075\n",
      "Epoch [3330/10000], Loss: 0.4555\n",
      "Epoch [3340/10000], Loss: 0.8706\n",
      "Epoch [3350/10000], Loss: 0.9383\n",
      "Epoch [3360/10000], Loss: 0.7436\n",
      "Epoch [3370/10000], Loss: 0.8466\n",
      "Epoch [3380/10000], Loss: 0.8379\n",
      "Epoch [3390/10000], Loss: 0.6832\n",
      "Epoch [3400/10000], Loss: 1.0414\n",
      "Epoch [3410/10000], Loss: 0.5256\n",
      "Epoch [3420/10000], Loss: 1.2059\n",
      "Epoch [3430/10000], Loss: 0.7949\n",
      "Epoch [3440/10000], Loss: 0.5962\n",
      "Epoch [3450/10000], Loss: 0.8650\n",
      "Epoch [3460/10000], Loss: 0.8154\n",
      "Epoch [3470/10000], Loss: 0.4271\n",
      "Epoch [3480/10000], Loss: 0.5725\n",
      "Epoch [3490/10000], Loss: 1.0792\n",
      "Epoch [3500/10000], Loss: 0.5633\n",
      "Epoch [3510/10000], Loss: 0.2986\n",
      "Epoch [3520/10000], Loss: 0.3941\n",
      "Epoch [3530/10000], Loss: 1.0033\n",
      "Epoch [3540/10000], Loss: 0.1960\n",
      "Epoch [3550/10000], Loss: 0.9863\n",
      "Epoch [3560/10000], Loss: 0.4395\n",
      "Epoch [3570/10000], Loss: 0.9612\n",
      "Epoch [3580/10000], Loss: 2.4734\n",
      "Epoch [3590/10000], Loss: 5.5539\n",
      "Epoch [3600/10000], Loss: 3.7807\n",
      "Epoch [3610/10000], Loss: 4.0435\n",
      "Epoch [3620/10000], Loss: 4.1143\n",
      "Epoch [3630/10000], Loss: 3.3714\n",
      "Epoch [3640/10000], Loss: 3.3396\n",
      "Epoch [3650/10000], Loss: 4.2713\n",
      "Epoch [3660/10000], Loss: 2.2012\n",
      "Epoch [3670/10000], Loss: 1.7168\n",
      "Epoch [3680/10000], Loss: 2.2133\n",
      "Epoch [3690/10000], Loss: 2.7070\n",
      "Epoch [3700/10000], Loss: 3.3160\n",
      "Epoch [3710/10000], Loss: 3.6073\n",
      "Epoch [3720/10000], Loss: 2.2879\n",
      "Epoch [3730/10000], Loss: 2.8893\n",
      "Epoch [3740/10000], Loss: 2.7971\n",
      "Epoch [3750/10000], Loss: 1.7426\n",
      "Epoch [3760/10000], Loss: 2.7662\n",
      "Epoch [3770/10000], Loss: 2.1203\n",
      "Epoch [3780/10000], Loss: 3.8798\n",
      "Epoch [3790/10000], Loss: 2.6662\n",
      "Epoch [3800/10000], Loss: 1.8491\n",
      "Epoch [3810/10000], Loss: 1.5527\n",
      "Epoch [3820/10000], Loss: 4.1708\n",
      "Epoch [3830/10000], Loss: 1.6162\n",
      "Epoch [3840/10000], Loss: 2.6064\n",
      "Epoch [3850/10000], Loss: 1.9516\n",
      "Epoch [3860/10000], Loss: 2.1771\n",
      "Epoch [3870/10000], Loss: 2.3933\n",
      "Epoch [3880/10000], Loss: 1.8314\n",
      "Epoch [3890/10000], Loss: 3.2097\n",
      "Epoch [3900/10000], Loss: 1.8215\n",
      "Epoch [3910/10000], Loss: 2.1805\n",
      "Epoch [3920/10000], Loss: 1.6260\n",
      "Epoch [3930/10000], Loss: 1.5388\n",
      "Epoch [3940/10000], Loss: 1.4422\n",
      "Epoch [3950/10000], Loss: 1.2274\n",
      "Epoch [3960/10000], Loss: 2.1992\n",
      "Epoch [3970/10000], Loss: 1.3654\n",
      "Epoch [3980/10000], Loss: 1.5024\n",
      "Epoch [3990/10000], Loss: 1.9630\n",
      "Epoch [4000/10000], Loss: 1.7742\n",
      "Epoch [4010/10000], Loss: 1.9968\n",
      "Epoch [4020/10000], Loss: 2.2213\n",
      "Epoch [4030/10000], Loss: 2.1302\n",
      "Epoch [4040/10000], Loss: 2.3094\n",
      "Epoch [4050/10000], Loss: 2.3253\n",
      "Epoch [4060/10000], Loss: 2.2924\n",
      "Epoch [4070/10000], Loss: 0.9917\n",
      "Epoch [4080/10000], Loss: 1.8697\n",
      "Epoch [4090/10000], Loss: 1.4888\n",
      "Epoch [4100/10000], Loss: 0.6206\n",
      "Epoch [4110/10000], Loss: 0.9877\n",
      "Epoch [4120/10000], Loss: 1.2839\n",
      "Epoch [4130/10000], Loss: 0.4944\n",
      "Epoch [4140/10000], Loss: 0.6533\n",
      "Epoch [4150/10000], Loss: 0.4354\n",
      "Epoch [4160/10000], Loss: 0.4216\n"
     ]
    }
   ],
   "source": [
    "# destroy_rand_weights(model)\n",
    "num_epochs = 10000\n",
    "batch_size = 1<<9\n",
    "train_err = []\n",
    "for epoch in range(num_epochs):\n",
    "    model.train()\n",
    "    data, labels = mkbatch(batch_size)\n",
    "    outputs = model(data)\n",
    "    loss = loss_fn(outputs, labels)\n",
    "    optimizer.zero_grad()\n",
    "    loss.backward()\n",
    "    optimizer.step()\n",
    "    train_err.append(loss.item())\n",
    "    if epoch % 10 == 0:\n",
    "        print(f\"Epoch [{epoch}/{num_epochs}], Loss: {loss.item():.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "execution_state": "idle",
   "id": "dcbdebf6-5c9f-4491-a442-9271d2ba5696",
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'plt' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[3], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mplt\u001b[49m\u001b[38;5;241m.\u001b[39msuptitle(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mMSE vs Epochs\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m      2\u001b[0m plt\u001b[38;5;241m.\u001b[39mplot(train_err, label\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mTrain\u001b[39m\u001b[38;5;124m'\u001b[39m, color\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mblue\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m      3\u001b[0m plt\u001b[38;5;241m.\u001b[39mxlabel(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mEpochs\u001b[39m\u001b[38;5;124m'\u001b[39m)\n",
      "\u001b[0;31mNameError\u001b[0m: name 'plt' is not defined"
     ]
    }
   ],
   "source": [
    "plt.suptitle('MSE vs Epochs')\n",
    "plt.plot(train_err, label='Train', color='blue')\n",
    "plt.xlabel('Epochs')\n",
    "plt.ylabel('MSE')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "execution_state": "idle",
   "id": "30893731-9991-4df9-b6c6-380010569ee1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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JKN169b24yQZXl5exo4dOxze3717dyMsLMz4448/HMY7depkWK1W+zWYOHGiIcn4/PPP7cecPXvWqFSpkiHJWLFihX38318vy5cvNyQZffr0yRH/P693QECAw/eVmTH+25kzZwxJRps2bS57jGEYxoMPPmhIMtLS0nI912zZ34f/dLnzy+v37IEDBwxJxowZM3LM8e+fVa+99prD1+TVNGrUyKhXr16O8RUrVhiSrrgFBATYj3fmaz+v319XOhdJhq+vr8O+d99915BkhIaG2v87GYZhDB06NMc8ucUQHx9vWCwW49ChQ/axLl26GJKMZ5991j5ms9mMVq1aGT4+PsbJkycd5lizZo0hyfjss89yzA8gdyzjgal8fX1zXV6SV1988YXuvvtulSpVSn/88Yd9i4mJUVZWllauXFngGP39/e3/Pn/+vP744w/7cpVNmzblOD67ypXt2WeflSR7S7h58+bJZrOpY8eODjGHhoaqcuXKWrFixWVjCQoK0tmzZ5WQkODUOdx3330qU6aMPvvsM/vY6dOnlZCQoIcffthh/t9//10bNmxwav6QkBD98ssvevrpp3X69GlNmzZNjz76qMqVK6eXX37Z/if1zZs368CBA+rXr1+Ov178s1rv7DXPlpCQoDNnzuiRRx5xuLbFihVTw4YN7df2+PHj2rJli7p06SKr1Wp///3336/q1avn+bx3796tsmXLqmzZsqpWrZomT56sVq1a5Vjmcs899zjMaxiG5s6dq9atW8swDIdYY2NjlZqaaj/P7777TmFhYerQoYP9/SVKlLBXqa9k7ty5slgsGjFiRI59V/rryLWM8c8//5QklSxZ8orHZe9PS0u76pzOutr3rJlOnTqlUqVKXXb/8OHDlZCQkGNr1qyZw3F5/dpXAb6//q1p06YOS6kaNmwoSWrfvr3Df8/s8d9++y3XGM6ePas//vhDd955pwzD0ObNm3N8Vu/eve3/zv5L04ULF7R06VKH47KvpStbIAOejmU8MNWNN95YoJs39+3bp61bt172z+QnTpwoQHR/S0lJ0ahRo/Tpp5/mmC81NTXH8ZUrV3Z4XbFiRXl5ednXrO7bt0+GYeQ4LtuVbrJ85pln9Pnnn6tFixa68cYb1axZM3Xs2FHNmze/4jl4e3urffv2mjNnjjIyMuTr66t58+YpMzPTIdkfPHiwli5dqttvv12VKlVSs2bN9Oijj+quu+664vySFBYWpqlTp+qdd97Rvn37tGTJEo0bN07Dhw9XWFiYnnzySf3666+SdNU/rzt7zbPt27dPuvTLTW4CAwMlSYcOHZJy+W8lSVWqVMlzwlOhQgW99957slgs8vPzU+XKlVWuXLkcx0VFRTm8PnnypM6cOaPp06dr+vTpuc6dfd6HDh1SpUqVciTnVapUuWp8v/76q8LDwxUcHJyn83FHjNlJYXbSfzl5/aUgP672PWu2K60vr1WrVq7r+T/++GOH13n92lcBvr/+LTIy0uF19i/OERERuY6fPn3aPnb48GENHz5cX3/9tcN4bjF4eXnluDH7lltukS7dT/FP2dfyar/MAvh/JPsw1T+rO3mRlZXl8Npms+n+++/XoEGDcj0++38IBdGxY0etWbNGAwcOVJ06dXTDDTfIZrOpefPmeboJ+N//07HZbLJYLFq0aJGKFSuW4/grtagsV66ctmzZoiVLlmjRokVatGiRZsyYoccff1wffvjhFePo1KmT3n33XS1atEht27bV559/rqpVq6p27dr2Y6pVq6Y9e/bom2++0eLFizV37ly98847Gj58uEaNGnXVc80+31tuuUW33HKLWrVqpcqVK2v27Nl68skn8/R+FeCaZ+/76KOPFBoammO/t7drf6QFBATk6cbKf3+dZ8f52GOPXfYeh1tvvdVFUebPtYrRarUqLCxMW7duveJxW7du1Y033mhPWi+XzP37Z0R+/HtuMz+rdOnSOZLd/HDma7+gP9Oy5fbz60rj2Yl4VlaW7r//fqWkpGjw4MGqWrWqAgICdPToUXXt2rVAzRWyr2X2PQYAro5kH25RqlSpHA9yuXDhgo4fP+4wVrFiRaWnp7usk8W/nT59WsuWLdOoUaM0fPhw+3h2FS03+/btc6jk7t+/Xzabzf7n7ooVK8owDEVFReXrlxEfHx+1bt1arVu3ls1m0zPPPKN3331Xw4YNu2Kf7saNGyssLEyfffaZGjVqpOXLl+vFF1/McVxAQIAefvhhPfzww7pw4YLatWunV155RUOHDnW6XeTNN9+sUqVK2f+7Zd8YuX379sv+N8vPNc+WPX+5cuWu+DWRfTNkbnPu2bPnqp9TUGXLllXJkiWVlZV11a/d8uXLa/v27TIMwyHpzEucFStW1JIlS5SSknLF6n5uyey1ilGSHnjgAb333ntavXq1GjVqlGP/qlWrdPDgQT311FP2sdx+Rugff7W52vn909W+Z7OXhvz78/LzWf9WtWpVzZ0716n35CavX/vOfH+ZVR3ftm2b9u7dqw8//FCPP/64ffxyyxNtNpt+++03h5+Xe/fulS79de2fDhw4IPEMF8AprNmHW1SsWDHHevvp06fnqKR17NhRa9eu1ZIlS3LMcebMGV28eLFAcWRXqP79Z/YrdfmZMmWKw+vJkydLklq0aCFd6thRrFgxjRo1Kse8hmFcsb3kv/d5eXnZq6tXa3Po5eWlDh06aOHChfroo4908eJFhyU8uc3v4+Oj6tWryzAMZWZmXnbu9evX6+zZsznGf/75Z506dcq+nOO2225TVFSUJk6cmCNxyr4W+bnm2WJjYxUYGKixY8fmGm92O8ywsDDVqVNHH374ocOSgYSEBO3cufOqn1NQxYoVU/v27TV37lxt3779snFKUsuWLXXs2DGHJ6lmt6m8mvbt28swjFz/KvPP6xsQEJDjv8e1ilGSBg4cKH9/fz311FM5vgZTUlL09NNPq0SJEvZWkLr0MyI1NdXhLwLHjx/PtX1qbuf3T1f7ng0MDFSZMmVy/Ex65513cv0s5fKLweVER0fr9OnTDuvZ8yOvX/vOfH85ey55lVsMhmFcsYXw22+/7XDs22+/reLFi6tp06YOxyUmJspqtapGjRoujRnwZFT24RZPPvmknn76abVv317333+/fvnlFy1ZsiTHn2YHDhyor7/+Wg888IC6du2qevXq6ezZs9q2bZu+/PJLHTx48Kp/zj158qTGjBmTYzwqKkpxcXFq3Lixxo8fr8zMTN144436/vvv7dWj3Bw4cEAPPvigmjdvrrVr1+rjjz/Wo48+al8uU7FiRY0ZM0ZDhw7VwYMH1bZtW5UsWVIHDhzQ/Pnz1bNnT4e+9/++LikpKbrvvvt000036dChQ5o8ebLq1KmTp0rWww8/rMmTJ2vEiBGqVatWjvc0a9ZMoaGhuuuuuxQSEqJdu3bp7bffVqtWra64Vvqjjz7S7Nmz9dBDD6levXry8fHRrl279MEHH8jPz8/+/AQvLy9NnTpVrVu3Vp06ddStWzeFhYVp9+7d2rFjh5YsWaLAwECnr3m2wMBATZ06VZ07d9Ztt92mTp06qWzZsjp8+LC+/fZb3XXXXfakIT4+Xq1atVKjRo30xBNPKCUlxf6MgfT09Kt+VkG9+uqrWrFihRo2bKgePXqoevXqSklJ0aZNm7R06VKlpKRIknr06KG3335bjz/+uBITExUWFqaPPvooTw+fu/fee9W5c2dNmjRJ+/btsy/TWLVqle699177TY/16tXT0qVLNWHCBIWHhysqKkoNGza8JjHq0pr5Dz/8UHFxcapVq1aOJ+j+8ccf+uSTTxxaZnbq1EmDBw/WQw89pD59+tjbTN5yyy057rm43Pllu9r3rC5977366qt68sknVb9+fa1cudJeXf73Z0nSiy++qE6dOql48eJq3bq1PXH+t1atWsnb21tLly7N0w3Nl5PXr31nvr+cPZe8qlq1qipWrKjnn39eR48eVWBgoObOnXvZ5Ux+fn5avHixunTpooYNG2rRokX69ttv9cILL+S4XyshIUGtW7dmzT7gDHe3A4JnuFzrzcu1eczKyjIGDx5slClTxihRooQRGxtr7N+/P0frTeNSy7mhQ4calSpVMnx8fIwyZcoYd955p/H6668bFy5cuGJc2e0/c9uaNm1qGIZh/P7778ZDDz1kBAUFGVar1fjPf/5jHDt2LEfLvew2fjt37jQ6dOhglCxZ0ihVqpTRu3dv46+//srx2XPnzjUaNWpkBAQEGAEBAUbVqlWNXr16GXv27LEf8+/2gl9++aXRrFkzo1y5coaPj48RGRlpPPXUU8bx48ev+t/AuNSyLiIiwpBkjBkzJsf+d99912jcuLFRunRpw9fX16hYsaIxcOBAIzU19Yrzbt261Rg4cKBx2223GcHBwYa3t7cRFhZm/Oc//zE2bdqU4/jVq1cb999/v1GyZEkjICDAuPXWW43Jkyfb9+f1mv+79Wa2FStWGLGxsYbVajX8/PyMihUrGl27djU2btzocNzcuXONatWqGb6+vkb16tWNefPmXbal47/lpW2rcalFYa9evXLdl5ycbPTq1cuIiIgwihcvboSGhhpNmzY1pk+f7nDcoUOHjAcffNAoUaKEUaZMGaNv377G4sWLr9p607jU7vS1114zqlatavj4+Bhly5Y1WrRoYSQmJtqP2b17t9G4cWPD39/fkOTwPebqGK9k69atxiOPPGKEhYXZP+uRRx4xtm3bluvx33//vVGzZk3Dx8fHqFKlivHxxx/n2nrzcufnzPfsuXPnjO7duxtW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zyO/nE7269Spo7feekvHjh3TBx98oOPHj6tRo0aqWbOmJkyYoJMnT5oTKQAAADxK9pp9szZnpKena8uWLdqyZYsk6cCBA9qyZYsOHz6szMxMdejQQRs3btTs2bOVlZWlpKQkJSUl6cKFC5KkatWqqXnz5urRo4d+/vln/fTTT+rdu7c6derktk48KshDtby9vdWuXTt98cUXGjdunPbv36/nn39eERERevzxx3X8+HHXRgoAAACYZOPGjapbt67q1q0rSRowYIDq1q2r4cOH6+jRo/r666/1+++/q06dOgoLC7Nva9assc8xe/ZsVa1aVU2bNlXLli3VqFEjTZ8+3Y1nlY81+9k2btyoDz74QJ9++qkCAgL0/PPPq3v37vr99981atQotWnThuU9AAAAuCzDxDX7zlb2mzRpois9fioPj6ZScHCw5syZ49Tnms3pZH/ChAmaMWOG9uzZo5YtW2rWrFlq2bKlvLz+/iNBVFSUZs6cqQoVKpgRLwAAAIA8cjrZnzp1qp544gl17dpVYWFhuR5Trlw5vf/++66IDwAAAB7KkJSHgnm+50Y+kv19+/Zd9RgfHx916dIlvzEBAAAAcIE83aB7+PBhpyY9evRofuMBAADAdcImi6kb8pjsN2jQQE899ZQ2bNhw2WNSU1P13nvvqWbNmpo7d64rYwQAAACQD3laxrNz50698soruv/+++Xn56d69eopPDxcfn5+On36tHbu3KkdO3botttu0/jx49WyZUvzIwcAAECRlp9++M7MjTxW9kuXLq0JEybo+PHjevvtt1W5cmX98ccf9vX7cXFxSkxM1Nq1a0n0AQAAgELCqRt0/f391aFDB3Xo0MG8iAAAAHBdsBkWWUyqwJvVv7+oyfcTdAEAAAAUbvl+gi4AAABQEIZhYp99Gu1LVPYBAAAAz0VlHwAAAG5BNx7zOV3ZX7lypS5evJhj/OLFi1q5cqWr4gIAAABQQE4n+/fee69SUlJyjKempuree+91VVwAAADwcNmVfbM25CPZNwxDFkvOi3fq1CkFBAQ4NdfKlSvVunVrhYeHy2KxaMGCBTk+a/jw4QoLC5O/v79iYmLsvf2zpaSkKC4uToGBgQoKClL37t2Vnp7u7GkBAAAAHifPa/bbtWsnSbJYLOratat8fX3t+7KysrR161bdeeedTn342bNnVbt2bT3xxBP2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De7OO+/U9OnTtXfvXt1yyy365ZdftHr1avtapQMHDigpKUkxMTH291itVjVs2FBr165Vp06dXBoPAAAAYKZ77733ir31ly9f7tR8Tif7N910k3755Rd99tln+uWXX5Senq7u3bsrLi5O/v7+zk53RUOGDFFaWpqqVq2qYsWKKSsrS6+88ori4uIkSUlJSZKkkJAQh/eFhITY9+UmIyNDGRkZ9tfZzwsAAAAA3KlOnToOrzMzM7VlyxZt375dXbp0cXo+p5P9lStX6s4771RcXJw96dal3vsrV65U48aNnQ7icj7//HPNnj1bc+bMUY0aNbRlyxb169dP4eHh+TrZbPHx8Ro1apTL4gQAAIDzLCZ2zSmaXfalN998M9fxkSNH5uuZVk6v2b/33nuVkpKSYzw1NVX33nuv0wFcycCBAzVkyBB16tRJtWrVUufOndW/f3/Fx8dLkkJDQyVJycnJDu9LTk6278vN0KFDlZqaat+OHDni0rgBAAAAV3rsscf0wQcfOP0+p5N9wzByXUd06tQpBQQEOB3AlZw7d05eXo4hFitWTDabTZIUFRWl0NBQLVu2zL4/LS1N69evV3R09GXn9fX1VWBgoMMGAACAa4wn6ObZ2rVr89VpMs/LeNq1aydd6r7TtWtX+fr62vdlZWVp69atLn20ryS1bt1ar7zyiiIjI1WjRg1t3rxZEyZM0BNPPGGPpV+/fhozZowqV65sb70ZHh6utm3bujQWAAAAuBitN3PIzrmzGYah48ePa+PGjRo2bJjT8+U52bdarfYPLFmypMPNuD4+Prrjjjtc/gTdyZMna9iwYXrmmWd04sQJhYeH66mnntLw4cPtxwwaNEhnz55Vz549debMGTVq1EiLFy+mxz4AAACKnOycO5uXl5eqVKmi0aNHq1mzZk7PZzH++TjaPBg1apQGDhyoEiVKOP1hhVVaWpqsVquaqI28LcXz8A4AAICi46KRqR/0lVJTUwvF8uXs3Kv82FfkZVKB1nb+vA698GKhOWd3cbobz+OPP66jR4+qcuXKDuP79u1T8eLFVaFCBVfGBwAAAFx3EhMTtWvXLklSjRo1VLdu3XzN4/QNul27dtWaNWtyjK9fv15du3bNVxAAAAC4/lgMc7ei6MSJE7rvvvvUoEED9enTR3369FG9evXUtGlTnTx50un5nE72N2/erLvuuivH+B133KEtW7Y4HQAAAACAvz377LP6888/tWPHDqWkpCglJUXbt29XWlqa+vTp4/R8Ti/jsVgs+vPPP3OMp6amKisry+kAAAAAcJ2iG08Oixcv1tKlS1WtWjX7WPXq1TVlypR83aDrdGW/cePGio+Pd0jss7KyFB8fr0aNGjkdAAAAAIC/2Ww2FS+es2FM8eLF7c+acobTlf1x48apcePGqlKliu6++25J0qpVq5SWlqbly5c7HQAAAACuU1T2c7jvvvvUt29fffLJJwoPD5ckHT16VP3791fTpk2dns/pyn716tW1detWdezYUSdOnNCff/6pxx9/XLt371bNmjWdDgAAAADA395++22lpaWpQoUKqlixoipWrKioqCilpaVp8uTJTs/ndGVfksLDwzV27Nj8vBUAAACQZG7XnKLajSciIkKbNm3S0qVLtXv3bklStWrVFBMTk6/58pTsb926VTVr1pSXl5e2bt16xWNvvfXWfAUCAAAAXK+WL1+u3r17a926dQoMDNT999+v+++/X7rUCKdGjRqaNm2afRl9XuUp2a9Tp46SkpJUrlw51alTRxaLRbk9eNdisdCRBwAAAHljWP7ezJq7CJk4caJ69OiR69N+rVarnnrqKU2YMMGcZP/AgQMqW7as/d8AAAAAXOeXX37RuHHjLru/WbNmev31152eN0/Jfvny5XP9NwAAAJBvdOOxS05OzrXlZjZvb+98PUE3T8n+119/necJH3zwQaeDAAAAAK5nN954o7Zv365KlSrlun/r1q0KCwtzet48Jftt27Z1eP3vNfsWy/+viWLNPgAAAPKCbjz/r2XLlho2bJiaN28uPz8/h31//fWXRowYoQceeMDpefPUZ99ms9m377//XnXq1NGiRYt05swZnTlzRt99951uu+02LV682OkAAAAAgOvdSy+9pJSUFN1yyy0aP368vvrqK3311VcaN26cqlSpopSUFL344otOz+v0Q7X69eunt956S7GxsQoMDFRgYKBiY2M1YcIE9enTx+kAAAAAcJ0yTN6ckJWVpWHDhikqKkr+/v6qWLGiXn75ZYfVLIZhaPjw4QoLC5O/v79iYmK0b98+l1yKkJAQrVmzRjVr1tTQoUP10EMP6aGHHtILL7ygmjVravXq1QoJCXF6XqcfqvXrr78qKCgox7jVatXBgwedDgAAAABwt3Hj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la9kf+PGjfr88891+PBhXbhwwWHfvHnzXBUbAAAAgAJw+qFan376qe68807t2rVL8+fPV2Zmpnbs2KHly5fLarWaEyUAAAA8TnY3HrM25CPZHzt2rN58800tXLhQPj4+euutt7R792517NiRVpwAAABAIeJ0sv/rr7+qVatWkiQfHx+dPXtWFotF/fv31/Tp082IEQAAAJ7IMHlz0tGjR/XYY4+pdOnS8vf3V61atbRx48b/D9cwNHz4cIWFhcnf318xMTHat2+fa6+Jizmd7JcqVUp//vmnJOnGG2/U9u3bJUlnzpzRuXPnXB8hAAAAYLLTp0/rrrvuUvHixbVo0SLt3LlTb7zxhkqVKmU/Zvz48Zo0aZKmTZum9evXKyAgQLGxsTp//rxbY78Sp2/Qbdy4sRISElSrVi395z//Ud++fbV8+XIlJCSoadOm5kQJAAAAj1OYnqA7btw4RUREaMaMGfaxqKgo+78Nw9DEiRP10ksvqU2bNpKkWbNmKSQkRAsWLFCnTp1cF7wLOV3Zf/vtt+0n8+KLL2rAgAFKTk5W+/bt9f7775sRIwAAAGCqr7/+WvXr19d//vMflStXTnXr1tV7771n33/gwAElJSUpJibGPma1WtWwYUOtXbvWTVFfndOV/eDgYPu/vby8NGTIEFfHBAAAgOvBNXiCblpamsOwr6+vfH19cxz+22+/aerUqRowYIBeeOEFbdiwQX369JGPj4+6dOmipKQkSVJISIjD+0JCQuz7CiOnK/vfffedlixZkmP8+++/16JFi1wVFwAAAFBgERERslqt9i0+Pj7X42w2m2677TaNHTtWdevWVc+ePdWjRw9NmzbtmsfsSk4n+0OGDFFWVlaOcZvNRpUfAAAAeXYt+uwfOXJEqamp9m3o0KG5xhIWFqbq1as7jFWrVk2HDx+WJIWGhkqSkpOTHY5JTk627yuMnE729+3bl+NCSFLVqlW1f/9+V8UFAAAAFFhgYKDDltsSHkm66667tGfPHoexvXv3qnz58tKlm3VDQ0O1bNky+/60tDStX79e0dHRJp9F/jmd7FutVv322285xvfv36+AgABXxQUAAABPV4j67Pfv31/r1q3T2LFjtX//fs2ZM0fTp09Xr169JEkWi0X9+vXTmDFj9PXXX2vbtm16/PHHFR4errZt25pzfVzA6WS/TZs26tevn379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",
      "text/plain": [
       "<Figure size 800x600 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "batch_src, batch_labels = map(lambda x: x.to(device), mkbatch(1<<10))\n",
    "model.eval()\n",
    "with torch.no_grad():\n",
    "    output = model(batch_src)\n",
    "\n",
    "# Flatten the arrays to 1D\n",
    "x = batch_labels.detach().to(torch.float16).cpu().numpy().flatten()\n",
    "y = output.detach().to(torch.float16).cpu().numpy().flatten()\n",
    "\n",
    "# Define the number of vertices and number of bins per dimension\n",
    "bins_y = 10 * NVTXS  # 10 * nvtxs for y-bin size\n",
    "\n",
    "# Initialize the 2D array (matrix) to store the counts\n",
    "count_matrix = np.zeros((NVTXS, bins_y), dtype=int)\n",
    "\n",
    "# Process the data: Map x to rows and floor(y*10) to columns\n",
    "for xi, yi in zip(x, y):\n",
    "    row = int(xi)  # Use integer value of x for row index\n",
    "    col = int(np.floor(yi * 10))  # Map y values to column by flooring and scaling by 10\n",
    "    if 0 <= row < NVTXS and 0 <= col < bins_y:  # Ensure valid indices\n",
    "        count_matrix[row, col] += 1\n",
    "\n",
    "# Transpose the matrix\n",
    "count_matrix = count_matrix.T\n",
    "\n",
    "# Plot the heatmap\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.imshow(count_matrix, cmap='viridis', origin='lower', interpolation='nearest', aspect='auto')\n",
    "\n",
    "# Set the labels and title\n",
    "plt.ylabel('Scaled Predicted Output (y)')\n",
    "plt.xlabel('True Labels (x)')\n",
    "plt.title('True Labels vs Scaled Predicted Output (Heatmap)')\n",
    "\n",
    "# Add a colorbar for reference\n",
    "plt.colorbar(label='Count')\n",
    "\n",
    "# Show the plot\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}