Jensen-holm commited on
Commit
4fb2726
1 Parent(s): 93618bd

modeling a given teams tournament score 5 number summary (minus standard

Browse files

deviation) using a pytorch neural network in 300k epochs. The model gets
439 for MSE Loss on testing data.

models/model5000.pth DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:9407aad0201670b6905fe1c5a994b648d4dd8e397294805726cddab13cb9ef9c
3
- size 34836
 
 
 
 
models/nn1M DELETED
Binary file (38.6 kB)
 
models/scoreDist30k.pth CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:87a3cc4c0aea78656441c8536aac0908299c6afe020e77df0f35b594be4e8caf
3
  size 39998
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d11fcf0f9b0ea5b93de0cbfbbaed4447f48ebc03c53be688981c3ceddbc287f7
3
  size 39998
src/mens_nn.ipynb CHANGED
@@ -20,34 +20,39 @@
20
  "outputs": [],
21
  "source": [
22
  "from sklearn.model_selection import train_test_split\n",
 
 
23
  "import torch\n",
24
  "import torch.nn as nn\n",
25
  "import torch.optim as optim\n",
26
- "import numpy as np\n",
27
  "import pandas as pd\n",
28
- "import os\n",
29
- "import matplotlib.pyplot as plt\n",
30
- "import seaborn as sns\n",
31
- "\n",
32
- "# set seaborn as the default plotting theme\n",
 
 
 
 
 
 
33
  "sns.set_theme()\n",
34
  "\n",
35
- "# check to make sure if there are any gpu's available for faster training\n",
36
  "def get_device() -> str:\n",
37
  " if torch.cuda.is_available():\n",
38
  " return \"cuda\"\n",
39
  " return \"cpu\"\n",
40
  "\n",
 
41
  "DEVICE = get_device()\n",
42
- "\n",
43
- "# universal data directory for this project\n",
44
- "DATA_DIR = os.path.join(\"..\", \"data\") \n",
45
- "MODEL_DIR = os.path.join(\"..\", \"models\")"
46
  ]
47
  },
48
  {
49
  "cell_type": "code",
50
- "execution_count": 2,
51
  "id": "b820f210",
52
  "metadata": {},
53
  "outputs": [
@@ -70,7 +75,7 @@
70
  },
71
  {
72
  "cell_type": "code",
73
- "execution_count": 3,
74
  "id": "02ebc500",
75
  "metadata": {},
76
  "outputs": [
@@ -4353,7 +4358,7 @@
4353
  "4 sec W02 "
4354
  ]
4355
  },
4356
- "execution_count": 3,
4357
  "metadata": {},
4358
  "output_type": "execute_result"
4359
  }
@@ -4373,7 +4378,7 @@
4373
  },
4374
  {
4375
  "cell_type": "code",
4376
- "execution_count": 4,
4377
  "id": "4901f37a",
4378
  "metadata": {},
4379
  "outputs": [
@@ -4460,7 +4465,7 @@
4460
  "4 64.0 "
4461
  ]
4462
  },
4463
- "execution_count": 4,
4464
  "metadata": {},
4465
  "output_type": "execute_result"
4466
  }
@@ -4492,7 +4497,7 @@
4492
  },
4493
  {
4494
  "cell_type": "code",
4495
- "execution_count": 5,
4496
  "id": "1251726e",
4497
  "metadata": {},
4498
  "outputs": [],
@@ -4508,7 +4513,7 @@
4508
  },
4509
  {
4510
  "cell_type": "code",
4511
- "execution_count": 6,
4512
  "id": "28478189",
4513
  "metadata": {},
4514
  "outputs": [
@@ -4623,7 +4628,7 @@
4623
  },
4624
  {
4625
  "cell_type": "code",
4626
- "execution_count": 7,
4627
  "id": "04f4a0a6",
4628
  "metadata": {},
4629
  "outputs": [
@@ -4651,7 +4656,7 @@
4651
  },
4652
  {
4653
  "cell_type": "code",
4654
- "execution_count": 8,
4655
  "id": "40094cd0",
4656
  "metadata": {},
4657
  "outputs": [],
@@ -4677,7 +4682,7 @@
4677
  },
4678
  {
4679
  "cell_type": "code",
4680
- "execution_count": 9,
4681
  "id": "e949ea09",
4682
  "metadata": {},
4683
  "outputs": [
@@ -4693,7 +4698,7 @@
4693
  " [ 0.0000, 570.0000, 44.0000, ..., 69.0000, 8.4030, 70.8889]])"
4694
  ]
4695
  },
4696
- "execution_count": 9,
4697
  "metadata": {},
4698
  "output_type": "execute_result"
4699
  }
@@ -4702,38 +4707,6 @@
4702
  "X_trainT"
4703
  ]
4704
  },
4705
- {
4706
- "cell_type": "markdown",
4707
- "id": "20bceb9a",
4708
- "metadata": {},
4709
- "source": [
4710
- "# Building Neural Network"
4711
- ]
4712
- },
4713
- {
4714
- "cell_type": "code",
4715
- "execution_count": 10,
4716
- "id": "3eb2a440",
4717
- "metadata": {},
4718
- "outputs": [],
4719
- "source": [
4720
- "def pred_vs_actual_plot(pred: torch.Tensor, actual: torch.Tensor, target_names: list[str]):\n",
4721
- " fig, axs = plt.subplots(2, len(pred[0]) // 2)\n",
4722
- " axs = axs.flatten()\n",
4723
- "\n",
4724
- " for i in range(len(pred[0])):\n",
4725
- " ax = axs[i]\n",
4726
- " ax.scatter(actual[:, i], pred[:, i], alpha=0.5)\n",
4727
- " ax.plot(ax.get_xlim(), ax.get_ylim(), color=\"red\", alpha=0.7, ls=\"--\")\n",
4728
- "\n",
4729
- " ax.set_title(f\"{target_names[i]}\")\n",
4730
- " ax.set_xlabel(f\"Actual\")\n",
4731
- " ax.set_ylabel(f\"Predicted\")\n",
4732
- "\n",
4733
- " plt.tight_layout()\n",
4734
- " plt.show()\n"
4735
- ]
4736
- },
4737
  {
4738
  "cell_type": "markdown",
4739
  "id": "40ef573f",
@@ -4781,72 +4754,136 @@
4781
  {
4782
  "cell_type": "code",
4783
  "execution_count": 12,
4784
- "id": "139797a0",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4785
  "metadata": {},
4786
  "outputs": [
4787
  {
4788
  "name": "stdout",
4789
  "output_type": "stream",
4790
  "text": [
4791
- "[30000 / 300000] MSE = 22.599327087402344 RMSE = 4.753874954960673\n",
4792
- "[60000 / 300000] MSE = 14.090914726257324 RMSE = 3.7537867182696094\n",
4793
- "[90000 / 300000] MSE = 10.638333320617676 RMSE = 3.261645799380686\n",
4794
- "[120000 / 300000] MSE = 8.183281898498535 RMSE = 2.860643616128814\n",
4795
- "[150000 / 300000] MSE = 6.67846155166626 RMSE = 2.584271957760301\n",
4796
- "[180000 / 300000] MSE = 5.694802761077881 RMSE = 2.386378587122731\n",
4797
- "[210000 / 300000] MSE = 10.18820571899414 RMSE = 3.1918968841418014\n",
4798
- "[240000 / 300000] MSE = 4.195699214935303 RMSE = 2.0483406003239066\n",
4799
- "[270000 / 300000] MSE = 4.009286403656006 RMSE = 2.002320255018164\n",
4800
- "[300000 / 300000] MSE = 3.245209217071533 RMSE = 1.8014464235917573\n"
4801
  ]
4802
  }
4803
  ],
4804
  "source": [
4805
- "\n",
4806
  "torch.manual_seed(3)\n",
4807
  "\n",
4808
  "scoreModel300k = MadnessNNScore()\n",
4809
- "optimizer = optim.Adam(lr=0.0001, params=scoreModel300k.parameters())\n",
4810
  "loss_fn = nn.MSELoss()\n",
4811
- "epochs = 300_000\n",
 
 
 
4812
  "\n",
4813
- "for epoch in range(1, epochs + 1):\n",
4814
- " pred = scoreModel300k(X_trainT)\n",
4815
- " loss = loss_fn(pred, y_trainT)\n",
4816
- " loss.backward()\n",
4817
- " optimizer.step()\n",
4818
- " optimizer.zero_grad()\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4819
  "\n",
4820
- " if epoch % (epochs // 10) == 0:\n",
4821
- " print(f\"[{epoch} / {epochs}] MSE = {loss.item()} RMSE = {np.sqrt(loss.item())}\")\n",
4822
- " "
 
4823
  ]
4824
  },
4825
  {
4826
  "cell_type": "code",
4827
- "execution_count": 13,
4828
- "id": "48578a20",
4829
  "metadata": {},
4830
  "outputs": [],
4831
  "source": [
4832
- "torch.save(\n",
4833
- " scoreModel300k,\n",
4834
- " os.path.join(MODEL_DIR, \"scoreDist30k.pth\"),\n",
4835
- ")"
 
 
 
 
 
 
 
 
 
 
 
 
4836
  ]
4837
  },
4838
  {
4839
  "cell_type": "code",
4840
- "execution_count": 14,
4841
- "id": "f9c4f31f",
4842
  "metadata": {},
4843
  "outputs": [
4844
  {
4845
  "name": "stdout",
4846
  "output_type": "stream",
4847
  "text": [
4848
- "MSE: 439.0569763183594\n",
4849
- "RMSE = 20.953686461297433\n"
4850
  ]
4851
  },
4852
  {
@@ -4861,13 +4898,28 @@
4861
  }
4862
  ],
4863
  "source": [
 
4864
  "scoreModel300k.eval()\n",
4865
  "\n",
 
4866
  "with torch.no_grad():\n",
4867
  " pred = scoreModel300k(X_testT)\n",
4868
  " loss = loss_fn(pred, y_testT)\n",
4869
- " print(f\"MSE: {loss}\\nRMSE = {np.sqrt(loss.item())}\")\n",
4870
- " pred_vs_actual_plot(pred, y_testT, target_names=target_df.columns)\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
4871
  ]
4872
  }
4873
  ],
 
20
  "outputs": [],
21
  "source": [
22
  "from sklearn.model_selection import train_test_split\n",
23
+ "import matplotlib.pyplot as plt\n",
24
+ "import seaborn as sns\n",
25
  "import torch\n",
26
  "import torch.nn as nn\n",
27
  "import torch.optim as optim\n",
 
28
  "import pandas as pd\n",
29
+ "import os"
30
+ ]
31
+ },
32
+ {
33
+ "cell_type": "code",
34
+ "execution_count": 2,
35
+ "id": "3f37c29e",
36
+ "metadata": {},
37
+ "outputs": [],
38
+ "source": [
39
+ "# set the plotting theme to seaborn \n",
40
  "sns.set_theme()\n",
41
  "\n",
 
42
  "def get_device() -> str:\n",
43
  " if torch.cuda.is_available():\n",
44
  " return \"cuda\"\n",
45
  " return \"cpu\"\n",
46
  "\n",
47
+ "# project constants\n",
48
  "DEVICE = get_device()\n",
49
+ "DATA_DIR = os.path.join(\"..\", \"data\")\n",
50
+ "MODEL_DIR = os.path.join(\"..\", \"models\")\n"
 
 
51
  ]
52
  },
53
  {
54
  "cell_type": "code",
55
+ "execution_count": 3,
56
  "id": "b820f210",
57
  "metadata": {},
58
  "outputs": [
 
75
  },
76
  {
77
  "cell_type": "code",
78
+ "execution_count": 4,
79
  "id": "02ebc500",
80
  "metadata": {},
81
  "outputs": [
 
4358
  "4 sec W02 "
4359
  ]
4360
  },
4361
+ "execution_count": 4,
4362
  "metadata": {},
4363
  "output_type": "execute_result"
4364
  }
 
4378
  },
4379
  {
4380
  "cell_type": "code",
4381
+ "execution_count": 5,
4382
  "id": "4901f37a",
4383
  "metadata": {},
4384
  "outputs": [
 
4465
  "4 64.0 "
4466
  ]
4467
  },
4468
+ "execution_count": 5,
4469
  "metadata": {},
4470
  "output_type": "execute_result"
4471
  }
 
4497
  },
4498
  {
4499
  "cell_type": "code",
4500
+ "execution_count": 6,
4501
  "id": "1251726e",
4502
  "metadata": {},
4503
  "outputs": [],
 
4513
  },
4514
  {
4515
  "cell_type": "code",
4516
+ "execution_count": 7,
4517
  "id": "28478189",
4518
  "metadata": {},
4519
  "outputs": [
 
4628
  },
4629
  {
4630
  "cell_type": "code",
4631
+ "execution_count": 8,
4632
  "id": "04f4a0a6",
4633
  "metadata": {},
4634
  "outputs": [
 
4656
  },
4657
  {
4658
  "cell_type": "code",
4659
+ "execution_count": 9,
4660
  "id": "40094cd0",
4661
  "metadata": {},
4662
  "outputs": [],
 
4682
  },
4683
  {
4684
  "cell_type": "code",
4685
+ "execution_count": 10,
4686
  "id": "e949ea09",
4687
  "metadata": {},
4688
  "outputs": [
 
4698
  " [ 0.0000, 570.0000, 44.0000, ..., 69.0000, 8.4030, 70.8889]])"
4699
  ]
4700
  },
4701
+ "execution_count": 10,
4702
  "metadata": {},
4703
  "output_type": "execute_result"
4704
  }
 
4707
  "X_trainT"
4708
  ]
4709
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4710
  {
4711
  "cell_type": "markdown",
4712
  "id": "40ef573f",
 
4754
  {
4755
  "cell_type": "code",
4756
  "execution_count": 12,
4757
+ "id": "a7449c1b",
4758
+ "metadata": {},
4759
+ "outputs": [],
4760
+ "source": [
4761
+ "\n",
4762
+ "def train_nn(model: nn.Module, X_trainT: torch.tensor, y_trainT: torch.Tensor, epochs: int, optimizer: optim.Optimizer, loss_fn) -> list[float]:\n",
4763
+ " loss_history = []\n",
4764
+ " for epoch in range(1, epochs + 1):\n",
4765
+ " pred = model(X_trainT)\n",
4766
+ " loss = loss_fn(pred, y_trainT)\n",
4767
+ " loss.backward()\n",
4768
+ " optimizer.step()\n",
4769
+ " optimizer.zero_grad()\n",
4770
+ "\n",
4771
+ " if epoch % (epochs // 10) == 0:\n",
4772
+ " print(f\"[{epoch} / {epochs}] {loss_fn} = {loss.item()}\")\n",
4773
+ " \n",
4774
+ " loss_history.append(loss.item())\n",
4775
+ " return loss_history\n"
4776
+ ]
4777
+ },
4778
+ {
4779
+ "cell_type": "code",
4780
+ "execution_count": 13,
4781
+ "id": "fd084d5f",
4782
  "metadata": {},
4783
  "outputs": [
4784
  {
4785
  "name": "stdout",
4786
  "output_type": "stream",
4787
  "text": [
4788
+ "[30000 / 300000] MSELoss() = 22.599327087402344\n",
4789
+ "[60000 / 300000] MSELoss() = 14.090914726257324\n",
4790
+ "[90000 / 300000] MSELoss() = 10.638333320617676\n",
4791
+ "[120000 / 300000] MSELoss() = 8.183281898498535\n",
4792
+ "[150000 / 300000] MSELoss() = 6.67846155166626\n",
4793
+ "[180000 / 300000] MSELoss() = 5.694802761077881\n",
4794
+ "[210000 / 300000] MSELoss() = 10.18820571899414\n",
4795
+ "[240000 / 300000] MSELoss() = 4.195699214935303\n",
4796
+ "[270000 / 300000] MSELoss() = 4.009286403656006\n",
4797
+ "[300000 / 300000] MSELoss() = 3.245209217071533\n"
4798
  ]
4799
  }
4800
  ],
4801
  "source": [
 
4802
  "torch.manual_seed(3)\n",
4803
  "\n",
4804
  "scoreModel300k = MadnessNNScore()\n",
 
4805
  "loss_fn = nn.MSELoss()\n",
4806
+ "optimizer = optim.Adam(\n",
4807
+ " lr=0.0001, \n",
4808
+ " params=scoreModel300k.parameters(),\n",
4809
+ ")\n",
4810
  "\n",
4811
+ "loss_history = train_nn(\n",
4812
+ " model=scoreModel300k,\n",
4813
+ " X_trainT=X_trainT,\n",
4814
+ " y_trainT=y_trainT,\n",
4815
+ " epochs=300_000,\n",
4816
+ " optimizer=optimizer,\n",
4817
+ " loss_fn=loss_fn,\n",
4818
+ ")"
4819
+ ]
4820
+ },
4821
+ {
4822
+ "cell_type": "code",
4823
+ "execution_count": 14,
4824
+ "id": "9e84417c",
4825
+ "metadata": {},
4826
+ "outputs": [
4827
+ {
4828
+ "data": {
4829
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkoAAAHJCAYAAAB67xZyAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABJ2klEQVR4nO3deVxU9f7H8fcMOwoKqOCSaRruhgsKKWpUtmgW6r1l6i3L8pZp1zX9aZumdc2l0spMzdS83kpbbLGyUn+VC1p5KzWXXMgERNBBBQaY8/uDH3ObZGSgQY7wej4ePmTO8uU7HxDefr/fc47FMAxDAAAAOI+1sjsAAABgVgQlAAAANwhKAAAAbhCUAAAA3CAoAQAAuEFQAgAAcIOgBAAA4AZBCQAAwA2CEoBqg/vrAigrghJQTQwdOlRDhw6t7G641aJFC82fP7/Efdu2bVOLFi20bdu2El+Xxm63a+bMmVq3bp3X+lteTz31lKZOnVrivuL3daE/mzdvvsg9liZNmqTExMSL/nkBM/Ct7A4AQFm1adNG//73v9W8eXOPjk9PT9frr7+up59+uoJ7VrrNmzdrwoQJFzzmscceU5s2bUrc16xZs4roFgA3CEoALjk1a9ZUTExMZXejzI4cOaLffvtN8fHxFzyuefPml+T7A6oipt4AuPj666915513qlOnTuratavGjRun48ePO/c7HA7NmzdPiYmJatu2rRITEzVnzhzl5+c7j/nggw/Ur18/tW/fXnFxcRo/frzS0tK81sc/Tr3l5ubqiSeeUI8ePdS2bVvdeOONWrJkiSTp119/1bXXXitJmjx5sssUUmnvde3atWrdurXeeustdevWTV26dNEbb7yhFi1a6NChQy59eu+999SqVSuX8/9o06ZN6tixo2rWrPmna7B27Vq1aNFCu3btUlJSktq3b69bbrlF69evdzkuOztbTz/9tK677jq1a9dOffv21dtvv+1yjGEYWrZsmW666Sa1b99e119/vZYsWXLemq61a9fqhhtuULt27dSvXz9t2rTpT78PwOwISgCc3n33Xd1zzz2qX7++5s6dq8mTJ+u7777T7bffrpMnT0qSXn31Vf3rX//SyJEjtXTpUg0aNEhLlizRyy+/LEnauXOnJk6cqN69e+vVV1/V5MmTtXXrVo0bN67Uz+9wOFRQUHDeH4fDccHzZs6cqc2bN+uRRx7RkiVLdO2112rWrFlas2aN6tWrpwULFkiSHnjgAefHnrxXSSosLNTSpUs1Y8YMTZ48WX379lVAQIDee++982oXHx+v+vXru+3npk2b1LNnz3LXobCw8LxjR4wYoWuvvVYLFixQ06ZN9Y9//MMZYHJzc3XnnXdq3bp1Gj58uF566SV16tRJU6ZM0cKFC51tzJo1S7NmzVJiYqIWLlyogQMHavbs2Vq0aJHzmOPHj2vRokV6+OGHNX/+fFksFo0ePdqlVkBVxNQbAElFv5xnz56t7t27a86cOc7tHTt21M0336wlS5Zo4sSJ2r59u9q2basBAwZIkrp06aKgoCCFhIRIKgpKgYGBuv/+++Xv7y9Jql27tn744QcZhiGLxeK2Dy+99JJeeumlMvd9+/bt6tatm/r06SNJ6tq1q4KDgxURESF/f3+1atVKktS4cWO1bt3a4/da7O9//7t69erlfH399dfr/fff18MPPyyLxaLU1FRt3bpVzz77rNs+5ubmKjk5WZMnTy71/dx9990lbr/yyiv1wQcfuGwbOnSoRo4cKUlKSEhQUlKSXnzxRfXs2VNr167Vvn37tHr1anXo0MF5TEFBgV566SXdcccdslqtWr58uYYMGeJcO3X11VfrxIkTSk5O1ogRIyQVfX+8+OKLzjVSAQEBuvvuu/X99987R+yAqoigBECSdOjQIZ04ceK8kZ/GjRurQ4cO2r59u6SiEDJnzhzdeeedSkxMVK9evTRkyBDn8bGxsZo3b5769u2rG264QT179lT37t09Gkn561//qr/+9a/nbf/pp5/0+OOPuz2va9euWr16tVJTU9WzZ0/17NnTGR7+zHstVhy0ig0cOFAffPCBduzYodjYWL377ruqUaOGrr/+erefc+vWrapTp45HC9CffPLJEhdzBwYGnrctKSnJ+bHFYtH111+v+fPnKzc3V9u3b1fDhg2dIalYv3799Pbbb2vXrl2yWCwqKChQ7969XY7545V5YWFhLgvJGzVqJKloag+oyph6AyBJOnXqlCSpTp065+2rU6eO8xfi8OHD9dhjjyk3N1ezZ89Wnz591LdvX23dulWS1KFDBy1atEiXXXaZXnvtNQ0ePFg9evTQihUrSu1DvXr11K5du/P+NG3a9ILnTZkyRf/4xz/066+/avr06bruuut0xx13aO/evX/q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LKxGuc8viX9A0ZcjMMDX621XVaGhQZXdhUsK9fIctfIctfIctfKcWWplmqDUvHlzSdKMGTO0a9curVy5UoGBgc5F2cWKw01wcLACAwMlSXa73flx8TFBQUUFLq2N8nI4DNls58p9fkny7QWSpNy8fGVlnfVq21WNj49VoaFBstlyVFjoqOzumB718hy18hy18hy18lxF1io0NKjMI1WVGpQyMzO1ZcsW3XDDDc41RFarVc2bN1d6erqioqKUnp7uck7x68jISBUUFDi3NW7c2OWYFi1aSFKpbfwZBQXe/QIWP+LN4TC83nZVVVjooFZlQL08R608R608R608Z5ZaVeoEYEZGhsaOHastW7Y4t+Xn52v37t1q1qyZYmNjtXPnThUWFjr3b926VU2bNlVERIRatmypmjVratu2bc79NptNu3fvVmxsrCSV2gYAAIA7lRqUoqOj1aNHDz311FNKTk7Wvn37NGnSJNlsNt19990aMGCAzpw5oylTpujAgQNau3atli1bphEjRkgqWps0ZMgQzZ49W59//rn27t2rMWPGKCoqSr1795akUtsAAABwp9LXKM2dO1dz5szRmDFjlJ2drc6dO+uNN95QgwYNJEmLFy/WjBkzlJSUpLp162rixIlKSkpynj969GgVFBRo6tSpys3NVWxsrJYsWSI/Pz9JUkRERKltmIWFGykBAGAqFsMwuA90ORQWOpSZ6d0F16s27NOGHb/qtoSm6tetqVfbrmp8fa0KC6uhrKyzppjDNjvq5Tlq5Tlq5Tlq5bmKrFV4eI0yL+Y2x00K4ILoCgCAORCUAAAA3CAoAQAAuEFQMiFm3gAAMAeCkolYuOwNAABTISiZEau5AQAwBYISAACAGwQlE2HiDQAAcyEomRATbwAAmANByUwYUgIAwFTK9ay3zMxMLVmyRN98841OnDihxYsXa8OGDWrZsqWuu+46b/cRAACgUpR5RCklJUX9+vXTm2++qcjISJ08eVKFhYU6dOiQRo8erY0bN1ZAN6sZ5t4AADCFMo8o/fOf/1RERIRWrFih4OBgtW3bVpI0Z84c5eXlaeHCherVq5e3+1ktMPMGAIC5lHlEacuWLXrwwQcVGhp63g0Sb7/9du3fv99rnQMAAKhM5VrM7etb8kCU3W7n7tJewMwbAADmUOag1LlzZ73yyis6d+6cc5vFYpHD4dC/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kQAAQHXkUVB6+umnK7ofUPE1b8y8AQBgFl5ZzP3HO3QDAABUBR4HpZSUFE2fPl2ff/65c9uGDRvUvXt3devWTQkJCfroo48qpJPVBmvjAQAwFY+m3lJSUvSXv/xFeXl5at26tSTp0KFD+sc//qHw8HBNmjRJv/zyi8aPH6969eqpc+fOFdrpqo6pNwAAzMGjoLRw4UKFh4fr9ddfV926dSUVPRS3sLBQs2fPVpcuXSQVXfX26quvEpQAAECV4NHU2zfffKN7773XGZIkafPmzapXr54zJElS7969tWvXLu/3spqwFM+98Rw9AABMwaOglJGRocaNGztfp6SkKDU1VV27dnU5LiQkRGfPnvVuDwEAACqJR0GpRo0astlsztfbt2+XxWJRXFycy3EpKSmqXbu2VzsIAABQWTwKSjExMS5XtL333nvy8fFRz549ndsMw9Cbb76p9u3be7+X1UTxI0yYeAMAwBw8Wsx933336a677lJqaqocDoe+++473X777YqIiJAkbdmyRa+//rq+//57vfbaaxXaYQAAgIvFoxGlTp066dVXX5Wfn5+ys7M1fPhwTZ061bl//Pjx2rZtm5544onzpuMAAAAuVR4/FDc+Pl7x8fEl7nv55ZfVpEkThYaGeq1j1RpzbwAAmILHQelCWJcEAACqIo+C0uTJkz1u0GKxaObMmeXuEAAAgFl4FJTeeecdWSwWRUZGymq98LImi4UHlpVXce0M5t4AADAFj4LSTTfdpI0bN8put+vGG29Unz591KlTp4ruGwAAQKXyKCjNmzdPOTk5+vLLL/XRRx9p2LBhqlOnjm6++Wb16dNHrVq1quh+VguMxQEAYC4eL+YOCgrSzTffrJtvvllnzpzRZ599po8++kjLli1To0aN1LdvX/Xp00dNmzatyP5WCzzqDQAAcyjXVW81a9ZUUlKSkpKSdOrUKX322Wf6+OOPtXDhQkVHR2vt2rXe7icAAMBF59ENJy8kLy9POTk5ys3NVWFhoY4dO+aNfgEAAFS6co0opaWlaf369Vq/fr127dql4OBgXXfddRoxYoS6devm7T4CAABUCo+D0u/D0ffff6+goCBdc801Gj58uBISEuTv71+R/QQAALjoPApKgwYN0q5duxQQEKCePXvq+eefV8+ePRUQEFDR/atWim9BxWJuAADMwaOg9N1338nHx0fN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",
4830
+ "text/plain": [
4831
+ "<Figure size 640x480 with 1 Axes>"
4832
+ ]
4833
+ },
4834
+ "metadata": {},
4835
+ "output_type": "display_data"
4836
+ }
4837
+ ],
4838
+ "source": [
4839
+ "# visualize the loss history over epoch\n",
4840
+ "sns.lineplot(\n",
4841
+ " x=[num for num in range(len(loss_history))],\n",
4842
+ " y=loss_history,\n",
4843
+ ")\n",
4844
  "\n",
4845
+ "plt.title(\"Loss History / Epoch\")\n",
4846
+ "plt.ylabel(\"MSE Loss Value\")\n",
4847
+ "plt.xlabel(\"Epoch\")\n",
4848
+ "plt.show()"
4849
  ]
4850
  },
4851
  {
4852
  "cell_type": "code",
4853
+ "execution_count": 15,
4854
+ "id": "e2d25270",
4855
  "metadata": {},
4856
  "outputs": [],
4857
  "source": [
4858
+ "\n",
4859
+ "def pred_vs_actual_plot(pred: torch.Tensor, actual: torch.Tensor, target_names: list[str]):\n",
4860
+ " fig, axs = plt.subplots(2, len(pred[0]) // 2)\n",
4861
+ " axs = axs.flatten()\n",
4862
+ "\n",
4863
+ " for i in range(len(pred[0])):\n",
4864
+ " ax = axs[i]\n",
4865
+ " ax.scatter(actual[:, i], pred[:, i], alpha=0.5)\n",
4866
+ " ax.plot(ax.get_xlim(), ax.get_ylim(), color=\"red\", alpha=0.7, ls=\"--\")\n",
4867
+ "\n",
4868
+ " ax.set_title(f\"{target_names[i]}\")\n",
4869
+ " ax.set_xlabel(f\"Actual\")\n",
4870
+ " ax.set_ylabel(f\"Predicted\")\n",
4871
+ "\n",
4872
+ " plt.tight_layout()\n",
4873
+ " plt.show()\n"
4874
  ]
4875
  },
4876
  {
4877
  "cell_type": "code",
4878
+ "execution_count": 16,
4879
+ "id": "734b90ee",
4880
  "metadata": {},
4881
  "outputs": [
4882
  {
4883
  "name": "stdout",
4884
  "output_type": "stream",
4885
  "text": [
4886
+ "MSELoss() = 439.0569763183594\n"
 
4887
  ]
4888
  },
4889
  {
 
4898
  }
4899
  ],
4900
  "source": [
4901
+ "# evaluate the seed 3 model\n",
4902
  "scoreModel300k.eval()\n",
4903
  "\n",
4904
+ "loss_fn = nn.MSELoss()\n",
4905
  "with torch.no_grad():\n",
4906
  " pred = scoreModel300k(X_testT)\n",
4907
  " loss = loss_fn(pred, y_testT)\n",
4908
+ " print(f\"{loss_fn} = {loss}\")\n",
4909
+ " pred_vs_actual_plot(pred, y_testT, target_df.columns)"
4910
+ ]
4911
+ },
4912
+ {
4913
+ "cell_type": "code",
4914
+ "execution_count": 17,
4915
+ "id": "48578a20",
4916
+ "metadata": {},
4917
+ "outputs": [],
4918
+ "source": [
4919
+ "torch.save(\n",
4920
+ " scoreModel300k,\n",
4921
+ " os.path.join(MODEL_DIR, \"scoreDist30k.pth\"),\n",
4922
+ ")"
4923
  ]
4924
  }
4925
  ],