diff --git a/CM20315_Coursework_I.ipynb b/CM20315_Coursework_I.ipynb new file mode 100644 index 00000000..521e9050 --- /dev/null +++ b/CM20315_Coursework_I.ipynb @@ -0,0 +1,447 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyONQTuflJTEoNl63WNZdEf7", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Coursework I -- Model hyperparameters\n", + "\n", + "The goal of the coursework is to modify a simple bit of numpy code that trains a network and measures the performance on a validation set for the MNist 1D dataset. \n", + "\n", + "In this coursework, you need to modify the **model hyperparameters** (only) to improve the performance over the current attempt. This could mean the number of layers, the number of hidden units per layer, or the type of activation function, or any combination of the three. \n", + "\n", + "The only constraint is that you MUST use a fully connected network (no convolutional networks for now if you have read ahead in the book).\n", + "\n", + "You don't have to improve the performance much. A few tenths of a percent is fine. It just has to be better to get full marks.\n", + "\n", + "You will need to upload three things to Moodle:\n", + "1. The image that this notebook saves (click the folder icon on the left on colab to download it)\n", + "2. The lines of code you changed\n", + "3. The whole notebook as a .ipynb file. You can do this on the File menu\n", + "\n", + "\n" + ], + "metadata": { + "id": "t9vk9Elugvmi" + } + }, + { + "cell_type": "code", + "source": [ + "import numpy as np\n", + "import os\n", + "import torch, torch.nn as nn\n", + "from torch.utils.data import TensorDataset, DataLoader\n", + "from torch.optim.lr_scheduler import StepLR\n", + "import matplotlib.pyplot as plt\n", + "import random" + ], + "metadata": { + "id": "YrXWAH7sUWvU" + }, + "execution_count": 1, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Run this once to copy the train and validation data to your CoLab environment \n", + "# or download from my github to your local machine if you are doing this locally\n", + "if not os.path.exists('./train_data_x.npy'):\n", + " !wget https://github.com/udlbook/udlbook/raw/main/practicals/train_data_x.npy\n", + " !wget https://github.com/udlbook/udlbook/raw/main/practicals/train_data_y.npy\n", + " !wget https://github.com/udlbook/udlbook/raw/main/practicals/val_data_x.npy\n", + " !wget https://github.com/udlbook/udlbook/raw/main/practicals/val_data_y.npy " + ], + "metadata": { + "id": "wScBGXXFVadm", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "970c192f-33ad-45ee-dc12-b1b9a30b50d7" + }, + "execution_count": 2, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "--2022-11-21 12:37:03-- https://github.com/udlbook/udlbook/raw/main/practicals/train_data_x.npy\n", + "Resolving github.com (github.com)... 140.82.114.3\n", + "Connecting to github.com (github.com)|140.82.114.3|:443... connected.\n", + "HTTP request sent, awaiting response... 302 Found\n", + "Location: https://raw.githubusercontent.com/udlbook/udlbook/main/practicals/train_data_x.npy [following]\n", + "--2022-11-21 12:37:04-- https://raw.githubusercontent.com/udlbook/udlbook/main/practicals/train_data_x.npy\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 1280128 (1.2M) [application/octet-stream]\n", + "Saving to: ‘train_data_x.npy’\n", + "\n", + "train_data_x.npy 100%[===================>] 1.22M --.-KB/s in 0.05s \n", + "\n", + "2022-11-21 12:37:04 (22.9 MB/s) - ‘train_data_x.npy’ saved [1280128/1280128]\n", + "\n", + "--2022-11-21 12:37:04-- https://github.com/udlbook/udlbook/raw/main/practicals/train_data_y.npy\n", + "Resolving github.com (github.com)... 140.82.112.3\n", + "Connecting to github.com (github.com)|140.82.112.3|:443... connected.\n", + "HTTP request sent, awaiting response... 302 Found\n", + "Location: https://raw.githubusercontent.com/udlbook/udlbook/main/practicals/train_data_y.npy [following]\n", + "--2022-11-21 12:37:04-- https://raw.githubusercontent.com/udlbook/udlbook/main/practicals/train_data_y.npy\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 32128 (31K) [application/octet-stream]\n", + "Saving to: ‘train_data_y.npy’\n", + "\n", + "train_data_y.npy 100%[===================>] 31.38K --.-KB/s in 0.002s \n", + "\n", + "2022-11-21 12:37:04 (13.9 MB/s) - ‘train_data_y.npy’ saved [32128/32128]\n", + "\n", + "--2022-11-21 12:37:04-- https://github.com/udlbook/udlbook/raw/main/practicals/val_data_x.npy\n", + "Resolving github.com (github.com)... 140.82.113.3\n", + "Connecting to github.com (github.com)|140.82.113.3|:443... connected.\n", + "HTTP request sent, awaiting response... 302 Found\n", + "Location: https://raw.githubusercontent.com/udlbook/udlbook/main/practicals/val_data_x.npy [following]\n", + "--2022-11-21 12:37:04-- https://raw.githubusercontent.com/udlbook/udlbook/main/practicals/val_data_x.npy\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 640128 (625K) [application/octet-stream]\n", + "Saving to: ‘val_data_x.npy’\n", + "\n", + "val_data_x.npy 100%[===================>] 625.12K --.-KB/s in 0.04s \n", + "\n", + "2022-11-21 12:37:05 (14.1 MB/s) - ‘val_data_x.npy’ saved [640128/640128]\n", + "\n", + "--2022-11-21 12:37:05-- https://github.com/udlbook/udlbook/raw/main/practicals/val_data_y.npy\n", + "Resolving github.com (github.com)... 140.82.114.4\n", + "Connecting to github.com (github.com)|140.82.114.4|:443... connected.\n", + "HTTP request sent, awaiting response... 302 Found\n", + "Location: https://raw.githubusercontent.com/udlbook/udlbook/main/practicals/val_data_y.npy [following]\n", + "--2022-11-21 12:37:05-- https://raw.githubusercontent.com/udlbook/udlbook/main/practicals/val_data_y.npy\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...\n", + "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 16128 (16K) [application/octet-stream]\n", + "Saving to: ‘val_data_y.npy’\n", + "\n", + "val_data_y.npy 100%[===================>] 15.75K --.-KB/s in 0s \n", + "\n", + "2022-11-21 12:37:05 (31.0 MB/s) - ‘val_data_y.npy’ saved [16128/16128]\n", + "\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "# Load in the data\n", + "train_data_x = np.load('train_data_x.npy')\n", + "train_data_y = np.load('train_data_y.npy')\n", + "val_data_x = np.load('val_data_x.npy')\n", + "val_data_y = np.load('val_data_y.npy')\n", + "# Print out sizes\n", + "print(\"Train data: %d examples (columns), each of which has %d dimensions (rows)\"%((train_data_x.shape[1],train_data_x.shape[0])))\n", + "print(\"Validation data: %d examples (columns), each of which has %d dimensions (rows)\"%((val_data_x.shape[1],val_data_x.shape[0])))" + ], + "metadata": { + "id": "8bKADvLHbiV5", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "869f360f-3f9b-4e3a-f8c6-cd69fb331709" + }, + "execution_count": 3, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Train data: 4000 examples (columns), each of which has 40 dimensions (rows)\n", + "Validation data: 2000 examples (columns), each of which has 40 dimensions (rows)\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Define the network" + ], + "metadata": { + "id": "_sFvRDGrl4qe" + } + }, + { + "cell_type": "code", + "source": [ + "# YOU SHOULD ONLY CHANGE THIS CELL!\n", + "\n", + "# There are 40 input dimensions and 10 output dimensions for this data\n", + "# The inputs correspond to the 40 offsets in the MNIST1D template.\n", + "D_i = 40\n", + "# The outputs correspond to the 10 digits\n", + "D_o = 10\n", + "\n", + "# Number of hidden units in layers 1 and 2\n", + "D_1 = 100\n", + "D_2 = 100\n", + "\n", + "# create model with two hidden layers\n", + "model = nn.Sequential(\n", + "nn.Linear(D_i, D_1),\n", + "nn.ReLU(),\n", + "nn.Linear(D_1, D_2),\n", + "nn.ReLU(),\n", + "nn.Linear(D_2, D_o))" + ], + "metadata": { + "id": "FslroPJJffrh" + }, + "execution_count": 4, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# He initialization of weights\n", + "def weights_init(layer_in):\n", + " if isinstance(layer_in, nn.Linear):\n", + " nn.init.kaiming_uniform_(layer_in.weight)\n", + " layer_in.bias.data.fill_(0.0)" + ], + "metadata": { + "id": "YgLaex1pfhqz" + }, + "execution_count": 5, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# You need all this stuff to ensure that PyTorch is deterministic\n", + "def set_seed(seed):\n", + " torch.manual_seed(seed)\n", + " torch.cuda.manual_seed_all(seed)\n", + " torch.backends.cudnn.deterministic = True\n", + " torch.backends.cudnn.benchmark = False\n", + " np.random.seed(seed)\n", + " random.seed(seed)\n", + " os.environ['PYTHONHASHSEED'] = str(seed)" + ], + "metadata": { + "id": "zXRmxCQNnL_M" + }, + "execution_count": 6, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Set seed so always get same result (do not change)\n", + "set_seed(1)\n", + "\n", + "# choose cross entropy loss function (equation 5.24 in the loss notes)\n", + "loss_function = nn.CrossEntropyLoss()\n", + "# construct SGD optimizer and initialize learning rate and momentum\n", + "optimizer = torch.optim.SGD(model.parameters(), lr = 0.05, momentum=0.9)\n", + "# object that decreases learning rate by half every 10 epochs\n", + "scheduler = StepLR(optimizer, step_size=10, gamma=0.5)\n", + "# create 100 dummy data points and store in data loader class\n", + "x_train = torch.tensor(train_data_x.transpose().astype('float32'))\n", + "y_train = torch.tensor(train_data_y.astype('long'))\n", + "x_val= torch.tensor(val_data_x.transpose().astype('float32'))\n", + "y_val = torch.tensor(val_data_y.astype('long'))\n", + "\n", + "# load the data into a class that creates the batches\n", + "data_loader = DataLoader(TensorDataset(x_train,y_train), batch_size=100, shuffle=True, worker_init_fn=np.random.seed(1))\n", + "\n", + "# Initialize model weights\n", + "model.apply(weights_init)\n", + "\n", + "# loop over the dataset n_epoch times\n", + "n_epoch = 50\n", + "# store the loss and the % correct at each epoch\n", + "losses_train = np.zeros((n_epoch))\n", + "errors_train = np.zeros((n_epoch))\n", + "losses_val = np.zeros((n_epoch))\n", + "errors_val = np.zeros((n_epoch))\n", + "\n", + "for epoch in range(n_epoch):\n", + " # loop over batches\n", + " for i, data in enumerate(data_loader):\n", + " # retrieve inputs and labels for this batch\n", + " x_batch, y_batch = data\n", + " # zero the parameter gradients\n", + " optimizer.zero_grad()\n", + " # forward pass -- calculate model output\n", + " pred = model(x_batch)\n", + " # compute the lss\n", + " loss = loss_function(pred, y_batch)\n", + " # backward pass\n", + " loss.backward()\n", + " # SGD update\n", + " optimizer.step()\n", + "\n", + " # Run whole dataset to get statistics -- normally wouldn't do this\n", + " pred_train = model(x_train)\n", + " pred_val = model(x_val)\n", + " _, predicted_train_class = torch.max(pred_train.data, 1)\n", + " _, predicted_val_class = torch.max(pred_val.data, 1)\n", + " errors_train[epoch] = 100 - 100 * (predicted_train_class == y_train).float().sum() / len(y_train)\n", + " errors_val[epoch]= 100 - 100 * (predicted_val_class == y_val).float().sum() / len(y_val)\n", + " losses_train[epoch] = loss_function(pred_train, y_train).item()\n", + " losses_val[epoch]= loss_function(pred_val, y_val).item()\n", + " print(f'Epoch {epoch:5d}, train loss {losses_train[epoch]:.6f}, train error {errors_train[epoch]:3.2f}, val loss {losses_val[epoch]:.6f}, percent error {errors_val[epoch]:3.2f}')\n", + " \n", + " # tell scheduler to consider updating learning rate\n", + " scheduler.step()\n", + "\n", + "# Plot the results\n", + "fig, ax = plt.subplots()\n", + "ax.plot(errors_train,'r-',label='train')\n", + "ax.plot(errors_val,'b-',label='validation')\n", + "ax.set_ylim(0,100); ax.set_xlim(0,n_epoch)\n", + "ax.set_xlabel('Epoch'); ax.set_ylabel('Error')\n", + "ax.set_title('Part I: Validation Result %3.2f'%(errors_val[-1]))\n", + "ax.legend()\n", + "ax.plot([0,n_epoch],[37.45, 37.45],'k:') # Original results. You should be better than this!\n", + "plt.savefig('Coursework_I_Results.png',format='png')\n", + "plt.show()" + ], + "metadata": { + "id": "NYw8I_3mmX5c", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "outputId": "777de5cf-c31d-4519-c7d3-e363e08d394c" + }, + "execution_count": 7, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Epoch 0, train loss 1.573925, train error 61.75, val loss 1.666198, percent error 67.85\n", + "Epoch 1, train loss 1.318168, train error 48.25, val loss 1.471983, percent error 57.95\n", + "Epoch 2, train loss 1.130921, train error 40.03, val loss 1.360329, percent error 53.45\n", + "Epoch 3, train loss 0.984176, train error 35.20, val loss 1.256625, percent error 48.10\n", + "Epoch 4, train loss 0.868384, train error 30.20, val loss 1.191934, percent error 45.30\n", + "Epoch 5, train loss 0.814746, train error 30.00, val loss 1.242606, percent error 48.25\n", + "Epoch 6, train loss 0.693850, train error 23.90, val loss 1.149572, percent error 43.70\n", + "Epoch 7, train loss 0.624264, train error 20.93, val loss 1.142540, percent error 41.20\n", + "Epoch 8, train loss 0.549854, train error 18.22, val loss 1.117410, percent error 40.55\n", + "Epoch 9, train loss 0.495227, train error 16.35, val loss 1.105867, percent error 40.25\n", + "Epoch 10, train loss 0.404633, train error 11.82, val loss 1.047640, percent error 37.90\n", + "Epoch 11, train loss 0.368226, train error 10.32, val loss 1.084517, percent error 40.00\n", + "Epoch 12, train loss 0.339168, train error 9.15, val loss 1.097698, percent error 38.50\n", + "Epoch 13, train loss 0.302940, train error 7.68, val loss 1.099108, percent error 37.25\n", + "Epoch 14, train loss 0.306518, train error 8.75, val loss 1.152268, percent error 39.40\n", + "Epoch 15, train loss 0.267522, train error 6.88, val loss 1.133403, percent error 38.20\n", + "Epoch 16, train loss 0.229632, train error 5.25, val loss 1.121083, percent error 37.15\n", + "Epoch 17, train loss 0.207498, train error 3.75, val loss 1.153062, percent error 38.00\n", + "Epoch 18, train loss 0.196556, train error 3.93, val loss 1.190135, percent error 37.40\n", + "Epoch 19, train loss 0.188664, train error 3.85, val loss 1.224324, percent error 37.10\n", + "Epoch 20, train loss 0.151122, train error 1.68, val loss 1.188515, percent error 36.50\n", + "Epoch 21, train loss 0.141133, train error 1.62, val loss 1.186356, percent error 36.30\n", + "Epoch 22, train loss 0.131978, train error 1.05, val loss 1.210334, percent error 37.10\n", + "Epoch 23, train loss 0.126643, train error 0.93, val loss 1.222403, percent error 37.15\n", + "Epoch 24, train loss 0.121445, train error 0.93, val loss 1.234944, percent error 36.60\n", + "Epoch 25, train loss 0.112892, train error 0.80, val loss 1.249163, percent error 36.35\n", + "Epoch 26, train loss 0.106721, train error 0.57, val loss 1.257951, percent error 37.40\n", + "Epoch 27, train loss 0.101724, train error 0.40, val loss 1.266331, percent error 36.90\n", + "Epoch 28, train loss 0.100189, train error 0.43, val loss 1.280694, percent error 37.50\n", + "Epoch 29, train loss 0.093124, train error 0.40, val loss 1.289725, percent error 37.50\n", + "Epoch 30, train loss 0.087898, train error 0.35, val loss 1.291468, percent error 36.75\n", + "Epoch 31, train loss 0.085375, train error 0.30, val loss 1.301522, percent error 37.60\n", + "Epoch 32, train loss 0.083599, train error 0.25, val loss 1.310020, percent error 37.40\n", + "Epoch 33, train loss 0.082141, train error 0.25, val loss 1.312388, percent error 37.00\n", + "Epoch 34, train loss 0.080171, train error 0.18, val loss 1.320177, percent error 37.05\n", + "Epoch 35, train loss 0.077832, train error 0.18, val loss 1.328110, percent error 37.40\n", + "Epoch 36, train loss 0.076884, train error 0.22, val loss 1.327245, percent error 36.75\n", + "Epoch 37, train loss 0.074366, train error 0.15, val loss 1.332270, percent error 37.35\n", + "Epoch 38, train loss 0.072928, train error 0.12, val loss 1.339683, percent error 37.25\n", + "Epoch 39, train loss 0.071071, train error 0.10, val loss 1.341762, percent error 37.20\n", + "Epoch 40, train loss 0.070039, train error 0.12, val loss 1.346855, percent error 37.35\n", + "Epoch 41, train loss 0.069533, train error 0.10, val loss 1.355226, percent error 37.45\n", + "Epoch 42, train loss 0.068655, train error 0.10, val loss 1.354576, percent error 37.60\n", + "Epoch 43, train loss 0.067851, train error 0.12, val loss 1.354539, percent error 37.05\n", + "Epoch 44, train loss 0.066937, train error 0.07, val loss 1.359383, percent error 37.70\n", + "Epoch 45, train loss 0.066291, train error 0.05, val loss 1.364250, percent error 37.55\n", + "Epoch 46, train loss 0.065502, train error 0.05, val loss 1.365553, percent error 37.55\n", + "Epoch 47, train loss 0.064782, train error 0.07, val loss 1.366146, percent error 37.55\n", + "Epoch 48, train loss 0.064185, train error 0.05, val loss 1.368595, percent error 37.35\n", + "Epoch 49, train loss 0.063420, train error 0.07, val loss 1.373254, percent error 37.45\n" + ] + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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fo4UULIQEclH165dc7RRGDt2LK+88goA3333HV988cVByaJVq1Z06tQJgJNOOolly5YlLd6SpGQhIqVTAVcAyVKzZs39z6dPn86///1vPvroI2rUqEHPnj3zbMNQrVq1/c8rV67Mjh07khJrSdMNbhGRSO3atdmyZUue723atIn69etTo0YNlixZwn//+98kR5daurIQEYk0bNiQHj160L59e6pXr07jxo33v9enTx8effRR2rZty/HHH0/37t1TGGnyWejDr2zS4Eci5cvixYtp27ZtqsMoV/I6pmY2290zirIdFUOJiEhcShYiIhKXkoWIiMSlZCEiInEpWYiISFxKFiIiEpeShYhIMdWqVQuAlStXcvHFF+e5TM+ePYlXxX/MmDFs3759/3xp7PJcyUJE5BAdeeSRvPTSS8VeP3eyKI1dnitZiIhEhg0bxrhx4/bP33XXXYwcOZLevXvTpUsXTjzxRF577bWD1lu2bBnt27cHYMeOHVx22WW0bduWCy64IEffUDfccAMZGRm0a9eOO++8EwidE65cuZIzzjiDM844Awhdnq9btw6ABx54gPbt29O+fXvGRP1lLVu2jLZt23L99dfTrl07zjrrrBLvg0rdfYhIqZSCHsoZMGAAN998MzfddBMAL7zwAv/6178YMmQIderUYd26dXTv3p3zzjsv3/GtH3nkEWrUqMHixYuZN28eXbp02f/eqFGjaNCgAXv37qV3797MmzePIUOG8MADDzBt2jQOO+ywHNuaPXs2Tz75JDNmzMDd6datG7FYjPr16ye9K3RdWYiIRDp37szatWtZuXIln376KfXr1+eII47gjjvuoEOHDpx55pmsWLGCNWvW5LuNd999d/9Ju0OHDnTo0GH/ey+88AJdunShc+fOLFy4kEWLFhUYz/vvv88FF1xAzZo1qVWrFhdeeCHvvfcekPyu0HVlISKlUqp6KL/kkkt46aWXWL16NQMGDOCZZ54hMzOT2bNnk5aWRsuWLfPsmjyepUuXcv/99/Pxxx9Tv359Bg0aVKztZEl2V+i6shARyWbAgAFMmjSJl156iUsuuYRNmzZx+OGHk5aWxrRp0/jmm28KXP/000/n2WefBWDBggXMmzcPgM2bN1OzZk3q1q3LmjVrePPNN/evk1/X6Keddhqvvvoq27dvZ9u2bbzyyiucdtppCfy0hacrCxGRbNq1a8eWLVto2rQpTZo0YeDAgfTr148TTzyRjIwM2rRpU+D6N9xwA9deey1t27albdu2nHTSSQB07NiRzp0706ZNG5o3b06PHj32rzN48GD69OnDkUceybRp0/a/3qVLFwYNGkTXrl0BuO666+jcuXNKRt9TF+UiUmqoi/LEUxflIiKSNEoWIiISl5KFiJQqZblovLRJ5LFUshCRUiM9PZ3169crYSSAu7N+/XrS09MTsj3VhhKRUqNZs2YsX76czMzMVIdSLqSnp9OsWbOEbEvJQkRKjbS0NFq1apXqMCQPKoYSEZG4UpIszOzXZrbQzBaY2XNmlm5mrcxshpl9aWbPm1nVVMQmIiIHS3qyMLOmwBAgw93bA5WBy4A/Ag+6+7HABuBnyY5NRETylqpiqCpAdTOrAtQAVgG9gKzRQyYA/VMUm4iI5JL0ZOHuK4D7gW8JSWITMBvY6O57osWWA03zWt/MBpvZLDObpRoTIiLJkYpiqPrA+UAr4EigJtCnsOu7+3h3z3D3jEaNGpVQlCIikl0qiqHOBJa6e6a77wZeBnoA9aJiKYBmwIoUxCYiInlIRbL4FuhuZjUsjEvYG1gETAMujpa5Bjh4oFsREUmJVNyzmEG4kf0JMD+KYTzwW+AWM/sSaAg8nuzYREQkbylpwe3udwJ35nr5a6BrCsIREZE41IJbRETiUrIQEZG4lCxERCQuJQsREYlLyUJEROJSshARkbiULEREJC4lCxERiUvJQkRE4lKyEBGRuJQsREQkLiULERGJS8lCRETiUrIQEZG4lCxERCQuJQsREYlLyUJEROJSshARkbiULEREJK4ynSwyM1MdgYhIxVCmk8WKFbBnT6qjEBEp/8p0sti7Fz74INVRiIiUf2U6WZjBa6+lOgoRkfKvTCeL2rXh1VfBPdWRiIiUb2U6WdSvD0uXwoIFqY5ERKR8K9PJom7dUBT16qupjkREpHwr08kiLQ26d1eyEBEpaWU6WQCcfz588gl8912qIxERKb/KfLLo3z88qlaUiEjJKfPJ4vjjoU0bJQsRkZJU5pMFhKKo6dNhw4ZURyIiUj6Vi2TRv3/o9uMf/0h1JCIi5VPZThbbtgHQtSsccYSKokRESkrZThbLlgFQqRKcdx68+Sb88ENqQxIRKY9SkizMrJ6ZvWRmS8xssZmdbGYNzGyqmX0RPdaPu6GdO+H774FQFLV1K7zzTklHLyJS8aTqyuJPwD/dvQ3QEVgMDAPedvfWwNvRfHwffQRAr15Qq5Ya6ImIlISkJwszqwucDjwO4O673H0jcD4wIVpsAtC/UBuM+iivVg369oUpU2DfvkRHLSJSsaXiyqIVkAk8aWZzzOwxM6sJNHb3VdEyq4HGea1sZoPNbJaZzdpTtSp8+OH+9/r3h9WrYebMkv4IIiIVSyqSRRWgC/CIu3cGtpGryMndHciz43F3H+/uGe6eUaVevZAZdu8G4JxzoEoVFUWJiCRaKpLFcmC5u8+I5l8iJI81ZtYEIHpcG3dLtWrBjh0wdy4A9epBz56qQisikmhJTxbuvhr4zsyOj17qDSwCpgDXRK9dA8Q/5desGR5zFUUtWRImERFJjFTVhvoV8IyZzQM6AfcA9wE/NrMvgDOj+YJVrQotWuQYiPu888LjxIkJj1lEpMIyL8NjkmZkZPis1q3h3Xdh+fIwEhJw+eUweTJ8/DF07JjiIEVEShkzm+3uGUVZp2y34Abo0QNWrswxoMVDD0HDhnDVVWrRLSKSCGU/WZxySnjMVhTVsCE8/jjMnw933pmiuEREypGynyw6dAg3urPd5IZQjfa662D06IPeEhGRIir7yaJKFejWLceVRZYHHgj3v6++en8HtSIiUgxlP1lAKIr69NPQk2A2tWvDU0/B11/DbbelJjQRkfKgfCSLHj1Ch1B59PMRi8Gvfw0PPwxvvZWC2EREyoG4ycLMKpnZKckIpti6dw+PeRRFAYwaBW3bwk9/qqFXRUSKI26ycPd9wLgkxFJ89epBu3b53slOT4e//S10MjhkSJJjExEpBwpbDPW2mV1kFrV6K4169AhjW+TTP3lGBgwfDk8/HUbUExGRwitssvg58CKwy8w2m9kWM9tcgnEV3SmnwKZNsGhRvovcfjscd1y4hxF1VCsiIoVQqGTh7rXdvZK7p7l7nWi+TkkHVyQ9eoTHAhpVVK0aqtN+9hmMK90FayIipUqha0OZ2Xlmdn80nVuSQRXLMcdAo0b53uTOcs45cPbZcNddkJmZnNBERMq6QiULM7sPGEroSnwRMNTM7i3JwIrMLBRFxWmubRauLrZuhf/93yTFJiJSxhX2yuIc4Mfu/oS7PwH0AX5ScmEVU48e8OWXsGZNgYudcALceCOMHw/z5iUpNhGRMqwojfLqZXteN9GBJERWp4IffRR30bvuCjVub74ZynAv7SIiSVHYZHEPMMfMnjKzCcBsYFTJhVVMJ50U7mIXoufABg1gxAiYNk1jdouIxFOoFtzAPqA78DIwGTjZ3Z8v4diKLj09JIw4N7mz/PznoS3fb36jcS9ERApS2Bbct7n7KnefEk2rkxBb8ZxyCsyaVaizf5UqMGZM6GhwzJgkxCYiUkYVthjq32b2GzNrbmYNsqYSjay4evSAXbvgk08KtfiZZ4Zxu0eOhFWrSjg2EZEyqrDJYgBwE/Au4X7FbGBWSQV1SLJucr/3XqFXuf/+cCFy221Fu9n98cehV9tZpfNIiIgkTGHvWQxz91a5pqOTEF/RNW4MXbrA84W/pdK6Nfz2t6HfqJtugr1746/z/vvQuze8+y5ceSXs2HEIMYuIlHKFvWfxP0mIJXEGDQrFUEVoRDFiREgYjzwCl11W8C2Pf/87tAJv0iQMrvTZZ/D73x9y1CIipVb5u2cBcPnlkJYGEyYUehUzuO++0Lr7pZegb1/YnEdXia+/DueeC0cfHa4qrrkGbrgBHnywSCVfIiJlinkhCunNbGkeL3uqi6IyMjJ8Vn43DC66KJQVLV8eEkcRPP00XHstnHhi6M68cePw+osvwhVXQKdO8M9/QsOG4fWtW6Fjx/D800+hVq1ifiARkSQws9nunlGUdQrb62zu+xWl955FlkGDYO3acFYvoiuvhL//PRQv9egBX30VBk+67DLo1i0UQ2UlCgjJ4amnYOnSUJQlIlLeFJgszOy2bM8vyfXePSUVVEL06RN6oS1CUVTu1d95BzZuhB/9KBQ3nXEG/OtfUDePzk5OOy10HfLwwyGZiIiUJ/GuLC7L9vz2XO/1SXAsiZWWFi4RpkyB9euLtYlu3UJJVr160L9/uF9Rs2b+y48aBccfH8b63rSpmHGLiJRC8ZKF5fM8r/nSZ9CgMCTec88VexNt2oSObF95JfQmUpDq1cOFzIoVcMstxd6liEipEy9ZeD7P85ovfTp0gM6dww2FQ1CpCH3zdusW7ls88QS88cYh7VZEpNSIdxrsmDXmNtAhep41f2IS4jt0gwbB7NmwYEHSdnnnnaEm1bXXhlbeIiJlXYHJwt0rZxtzu0r0PGu+aPVRU+WKK4rc5uJQVasGL7wQ7m+cdhpMnJi0XYuIlIiiDH5UNh12GPzkJ+GMvWdP0nbbpk24qjjlFLj66tANehJ3LyKSUOU/WUAoilqzJtR7TaLDDgu7/NWv4P/+D845B77/PqkhiIgkRMVIFuecE9pcHOKN7uJIS4OxY+Gxx2D6dOjaFRYuTHoYIiKHpGIki7Q0GDgwtLlI0U/7n/0sJIutW6F7dxg9OgwVrt5qRaQsSFmyMLPKZjbHzF6P5luZ2Qwz+9LMnjezqgnd4aBBYVCkSZMSutmiyBrEr0OHMHbGKadAnTphJNgbboAnnwyVtrZtS1mIIiJ5KlRHgiWyY7NbgAygjrufa2YvAC+7+yQzexT41N0fKWgbBXYkmJfOncNVxsyZhxJ6QqxcGW6Az5wZpo8/ztnqu3ZtOOKI0A161tS2baiOW8R+EUVEcihOR4IpSRZm1gyYAIwCbgH6AZnAEe6+x8xOBu5y97ML2k6Rk8Wf/hQ6cJoxI9w8KEX27YMvvghNQr77Lgzxmnvatg1OPTVcHDVtGn+b77wDt98Oxx0Hd9wRko2ISFlKFi8B9wK1gd8Ag4D/uvux0fvNgTfdvX0e6w4GBgO0aNHipG+++abwO960KZwxjzgi/JyvUuVQP0pSPfssDB4MNWqE52eemfdy27fDsGHw5z/DUUdBZma4N3LxxfC73x3oTr2oduyAhx4KvexefnnoM0tEyp4S66I8kczsXGCtu88uzvruPt7dM9w9o1GjRkVbuW7dcLabMyeMVlTGXHFFKK5q1AjOOgv+8IdwRZLdf/8bStv+/GcYMgQWLYJvvglXGP/8ZxiL4/zzi96yfM4cyMgI91puvDEUi119dRgAKkUlmSKSTO6e1IlwRbEcWAasBrYDzwDrgCrRMicD/4q3rZNOOsmLpX9/9/R09y+/LN76KbZ1q/vAge7gfvbZ7pmZ7jt3ut9+u3ulSu4tWri//fbB633/vfvdd7vXrx/WPess99dfd9+zJ/997dnjfs897mlp7kce6f7WW+6zZrn/4hfudeqE7bRu7f7HP7qvWlVyn1lEEgeY5UU9dxd1hUROQE/g9ej5i8Bl0fNHgRvjrV/sZLF8eTjT9e7tvm9f8baRYvv2uT/6qHvVqu7Nmrl36BD+mtde675xY8Hrbtrkft997o0bh3WOOsp91KiDT/Zff+1+6qlhmUsvdV+/Puf727a5T5jgftppYZnKlUPyeuIJ9w0bCo5h9273Dz90HzEi7PvNN91Xry7yYRCRYijryeJoYCbwZZQ4qsVbv1atWv7kk0+6u/uuXbs8Fov5xIkT3d1927ZtHovFfNKkSe7uvnHjRo/FYj558mR3d88cPdpj4FOGDnV391WrVnksFvM333zT3d2//fZbj8ViPnXqVHd3/+qrrzwWi/n06dPd3X3JkiUei8X8gw8+cHf3+fPneywW85kzZ7q7+5w5czwWi/mcOXPc3X3mzJkei8V8/vz57u7+wQcfeCwW8yVLlri7+/Tp0z0Wi/lXX33l7u5Tp071WCzm3377rbu7v/nmmx6LxXxVdEafMmWKx2Ix//e/M71VK/e6dSd7+/Yx3xhlikmTJnksFvNt27a5u/vEiRM9Fov5rl273N39ySef9NNPj/mLL4acCePdrLdfckm4KrnqqnFeuXIfr1vX/emn3R98cIz369dv/5dt9OjRfuGFF+6fv+WWe71t2wF+9NHhW1Wp0ghv2nSgP/20++bN7sOHD/dLLhnkf/2r+8UXu1erNszhejcLy8OtDjf6kUe6n3uue7duQ/3cc4f6J5+EJHbDDTf6rbfeun9/119/vQ8bNmz//KBBg3z48OH75wcOHOgjRozYPz9gwAC/9957989feOGFPnr06P3z/fr18zFjxuyf79Onj48bN27/fO/evX38+PH752OxWLG+eytXuv/jH5neo0fMp0yZ4u75f/feemuqL1/uPn78V96qVcyvvXa6jxjhPmTIEm/ZMubXXfeBjxjhfttt871Ll5h/+GHB37158+b755+7//rXH3ijRjHv23eJ//zn7ldcMd2PPjrmI0d+5S+84D569FTv1i3mX31V8HcvMzPT3d0nT57ssdjB370VK7b5vHnuv/vdRO/RI+d3LxaL7T+W48eP9969e++ff+ihcd6zZx///HP3b791HzlyjPft28937877u3fvvff6gAED9s+PGDHCBw4c6Hv3hu/ezTcP9wsvHORz5rh/9JH7FVcM8z59rveXX3Z/9ln3s8661U899UYfNSpcnXfsONTbth3qF13kftFF7hkZN3q/frf6kiXhSjv3d+/yywf5wIHDfeRI9/POc69RY6DXrj3Cjz/ePSPD/fDDB3ibNvf6FVe4X3ede8uWF3q3bqN98GD3wYPdW7To5yefPGb/fPPmfbxHj3H755s27e1nnjne777bfexY9zZtYj506JP+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\n" + }, + "metadata": { + "needs_background": "light" + } + } + ] + }, + { + "cell_type": "code", + "source": [ + "# Leave this all commented for now\n", + "# We'll see how well you did on the test data after the coursework is submitted\n", + "\n", + "# if not os.path.exists('./test_data_x.npy'):\n", + "# !wget https://github.com/udlbook/udlbook/raw/main/practicals/test_data_x.npy\n", + "# !wget https://github.com/udlbook/udlbook/raw/main/practicals/test_data_y.npy\n", + "\n", + "\n", + "# # I haven't given you this yet, leave commented\n", + "# test_data_x = np.load('test_data_x.npy')\n", + "# test_data_y = np.load('test_data_y.npy')\n", + "# x_test = torch.tensor(test_data_x.transpose().astype('float32'))\n", + "# y_test = torch.tensor(test_data_y.astype('long'))\n", + "# pred_test = model(x_test)\n", + "# _, predicted_test_class = torch.max(pred_test.data, 1)\n", + "# errors_test = 100 - 100 * (predicted_test_class == y_test).float().sum() / len(y_test)\n", + "# print(\"Test error = %3.3f\"%(errors_test))" + ], + "metadata": { + "id": "O7nBz-R84QdJ" + }, + "execution_count": 8, + "outputs": [] + } + ] +} \ No newline at end of file