{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"}],"dockerImageVersionId":30761,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# CNN based Digit Recognizer using PyTorch-lightning\n* This notebook is designed for those with intermediate-level knowledge of computer vision and machine learning.\n* To provide hands-on experience, we are using `lightning` to help go over the entire training and evaluation processes.\n* The result of this notebook is [0.9949, 0.9958]\n","metadata":{}},{"cell_type":"code","source":"!pip install lightning","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:09.489258Z","iopub.execute_input":"2024-09-23T02:44:09.489695Z","iopub.status.idle":"2024-09-23T02:44:23.604323Z","shell.execute_reply.started":"2024-09-23T02:44:09.489652Z","shell.execute_reply":"2024-09-23T02:44:23.603049Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Import all libraries","metadata":{}},{"cell_type":"code","source":"# Import libraries\nimport glob\nimport random\nimport numpy as np\nimport pandas as pd \nfrom tqdm import tqdm\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import accuracy_score\n\n# Pytorch\nimport torch\nimport torch.nn as nn\nfrom torch.nn import init\nimport torch.optim as optim\nfrom torch.utils import data\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\nfrom torchvision.transforms import functional as TF\n\n# Lightning\nimport lightning as L\nimport pytorch_lightning as pl\nfrom lightning.pytorch import Trainer\nfrom lightning.pytorch.callbacks import RichProgressBar, ModelCheckpoint, LearningRateMonitor, EarlyStopping","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:23.606695Z","iopub.execute_input":"2024-09-23T02:44:23.607058Z","iopub.status.idle":"2024-09-23T02:44:23.615208Z","shell.execute_reply.started":"2024-09-23T02:44:23.607019Z","shell.execute_reply":"2024-09-23T02:44:23.614254Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Check the data","metadata":{}},{"cell_type":"code","source":"# Load data\n\ntrain_dataframe = pd.read_csv('/kaggle/input/digit-recognizer/train.csv')\ntest_dataframe = pd.read_csv('/kaggle/input/digit-recognizer/test.csv')","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:23.616621Z","iopub.execute_input":"2024-09-23T02:44:23.616936Z","iopub.status.idle":"2024-09-23T02:44:28.434485Z","shell.execute_reply.started":"2024-09-23T02:44:23.616903Z","shell.execute_reply":"2024-09-23T02:44:28.433357Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Check the data size\nprint(f'Number of Trainset samples: {len(train_dataframe)}')\nprint(f'Number of Testset samples: {len(test_dataframe)}')","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:28.436932Z","iopub.execute_input":"2024-09-23T02:44:28.437299Z","iopub.status.idle":"2024-09-23T02:44:28.442393Z","shell.execute_reply.started":"2024-09-23T02:44:28.437265Z","shell.execute_reply":"2024-09-23T02:44:28.441420Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dataframe.head(3)","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:28.443604Z","iopub.execute_input":"2024-09-23T02:44:28.444011Z","iopub.status.idle":"2024-09-23T02:44:28.472517Z","shell.execute_reply.started":"2024-09-23T02:44:28.443966Z","shell.execute_reply":"2024-09-23T02:44:28.471374Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Dataset & Dataloader\nIn this section, we will build a custom dataset and dataloader for MNIST dataset.","metadata":{}},{"cell_type":"markdown","source":"#### Custom MNIST dataset class","metadata":{}},{"cell_type":"code","source":"class MNIST(Dataset):\n    def __init__(self, set_name, augmentation):\n        self.set_name = set_name # It should be one betweeb 'train', and 'test'\n        self.augmentation = augmentation\n        if set_name == 'test':\n            # Load test data\n            self.data = pd.read_csv('/kaggle/input/digit-recognizer/test.csv').values.astype('float32')\n            self.labels = None  # No labels in test set\n            print('Test data shape:', self.data.shape)\n        else:\n            # Load training data\n            df = pd.read_csv('/kaggle/input/digit-recognizer/train.csv')\n            self.labels = df['label'].values.astype('int64')  # Ensure labels are integers\n            self.data = df.drop('label', axis=1).values.astype('float32')\n\n            if set_name == 'val':\n                # Select every 20th element\n                self.data = self.data[::20]\n                self.labels = self.labels[::20]\n                print('Validation data shape:', self.data.shape)\n            elif set_name == 'train':\n                # Remove every 20th element\n                indices = np.delete(np.arange(len(self.data)), np.arange(0, len(self.data), 20))\n                self.data = self.data[indices]\n                self.labels = self.labels[indices]\n                print('Training data shape:', self.data.shape)\n            else:\n                raise ValueError(\"set_name must be one of ['train', 'val', 'test']\")\n        \n        self.data = torch.from_numpy(self.data)\n        \n        # Additional member variables for data augmentation\n        self.img2tensor = transforms.Compose([transforms.ToTensor()])\n        self.tensor2img = transforms.Compose([transforms.ToPILImage()])\n        self.input_size = 28\n    \n    def __len__(self):\n        return len(self.data)\n    \n    # Member function for reshaping a flatten tensor 1 x 784 to 2D image tensor 28 x 28.\n    def flatten2img(self, row):\n        return row.reshape((28, 28))\n    \n    # Member function for data augmentation\n    # Data Augmentaion may help for generalization\n    def augment_data(self, img_tensor):\n        if not self.augmentation or self.set_name == 'test':\n            return img_tensor\n\n        # Random Erasing\n        if random.random() > 0.3:\n            max_size = 8\n            h = random.randint(1, max_size)\n            w = random.randint(1, max_size)\n            i = random.randint(0, self.input_size - h)\n            j = random.randint(0, self.input_size - w)\n            v = random.uniform(0.0, 1.0)  # Since pixel values are in [0,1]\n            img_tensor = TF.erase(img_tensor, i, j, h, w, v)\n\n        # Convert the img_tensor to a PIL image\n        img = self.tensor2img(img_tensor)\n\n        # Random Shift, Shear, Rotation\n        shift_x = random.randint(-5, 5)\n        shift_y = random.randint(-5, 5)\n        shear = random.uniform(-15, 15)\n        angle = random.randint(-15, 15)\n        img = TF.affine(\n            img,\n            angle=angle,\n            translate=(shift_x, shift_y),\n            scale=1,\n            shear=shear\n        )\n\n        # Reconvert the PIL image back to a tensor\n        img_tensor = self.img2tensor(img)\n\n        # Adding Random Values in Random Region\n        if random.random() > 0.3:\n            max_size = 8\n            h = random.randint(1, max_size)\n            w = random.randint(1, max_size)\n            i = random.randint(0, self.input_size - h)\n            j = random.randint(0, self.input_size - w)\n            # Generate random noise to add\n            random_noise = torch.rand(1, h, w)\n            # Add the random noise to the selected region\n            img_tensor[:, i:i+h, j:j+w] += random_noise\n            # Ensure pixel values are within [0,1]\n            img_tensor = torch.clamp(img_tensor, 0.0, 1.0)\n\n        return img_tensor\n        \n    # This function is about how to get a data sample for a given index\n    def __getitem__(self, idx):\n        if self.set_name == 'test':\n            image = self.flatten2img(self.data[idx]) / 255.0\n            image = torch.unsqueeze(image, 0)\n            label = torch.zeros(10, dtype=torch.float16)\n            return image, label\n        image = self.flatten2img(self.data[idx]) / 255.0\n        # If augmentation is True, then apply our preset data augmentation\n        if self.augmentation:\n            image = self.augment_data(image)\n        else:\n            image = torch.unsqueeze(image, 0)\n        label = torch.zeros(10, dtype=torch.float16)\n        label[int(self.labels[idx])] = 1.0\n        \n        return image, label","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:28.473960Z","iopub.execute_input":"2024-09-23T02:44:28.474333Z","iopub.status.idle":"2024-09-23T02:44:28.505152Z","shell.execute_reply.started":"2024-09-23T02:44:28.474294Z","shell.execute_reply":"2024-09-23T02:44:28.503934Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Check the custom dataset","metadata":{}},{"cell_type":"code","source":"sampleset = MNIST('train', True)","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:28.506524Z","iopub.execute_input":"2024-09-23T02:44:28.506818Z","iopub.status.idle":"2024-09-23T02:44:31.592074Z","shell.execute_reply.started":"2024-09-23T02:44:28.506786Z","shell.execute_reply":"2024-09-23T02:44:31.591076Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"n = len(sampleset)\nrand_idx = random.randint(0, n)\nimg, label = sampleset[rand_idx]\nimg = img * 255.0\nplt.figure()\nplt.title(f'Sample: {torch.argmax(label).item()}')\nplt.imshow(img[0])","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:31.593411Z","iopub.execute_input":"2024-09-23T02:44:31.593776Z","iopub.status.idle":"2024-09-23T02:44:31.907695Z","shell.execute_reply.started":"2024-09-23T02:44:31.593740Z","shell.execute_reply":"2024-09-23T02:44:31.906737Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Custom MNIST dataloader class","metadata":{}},{"cell_type":"code","source":"class MNIST_loader():\n    def __init__(self, set_name, augmentation, batch_size, n_workers, shuffle):\n        self.set_name = set_name\n        self.augmentation = augmentation\n        self.batch_size = batch_size\n        self.n_workers = n_workers\n        self.shuffle = shuffle\n\n    def get_data_loader(self):\n        dataset = MNIST(self.set_name, self.augmentation)\n        data_loader = data.DataLoader(\n            dataset,\n            batch_size=self.batch_size,\n            shuffle=self.shuffle,\n            num_workers=self.n_workers,\n            drop_last=False\n        )\n        return data_loader","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:31.909039Z","iopub.execute_input":"2024-09-23T02:44:31.909460Z","iopub.status.idle":"2024-09-23T02:44:31.917991Z","shell.execute_reply.started":"2024-09-23T02:44:31.909417Z","shell.execute_reply":"2024-09-23T02:44:31.916897Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## CNN Based Backbone","metadata":{}},{"cell_type":"markdown","source":"#### Custom Residual Block","metadata":{}},{"cell_type":"code","source":"class ResidualBlock(nn.Module):\n    def __init__(self, in_channels, out_channels, stride=1):\n        super(ResidualBlock, self).__init__()\n        \n        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(out_channels)\n        self.relu = nn.ReLU()\n        \n        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm2d(out_channels)\n        \n        # This is for the skip connection.\n        self.skip = nn.Sequential()\n        if stride != 1 or in_channels != out_channels:\n            self.skip = nn.Sequential(\n                nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),\n                nn.BatchNorm2d(out_channels)\n            )\n\n    def forward(self, x):\n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = self.relu(out)\n        \n        out = self.conv2(out)\n        out = self.bn2(out)\n        \n        # Apply the skip connection\n        out += self.skip(x)\n        out = self.relu(out)\n        \n        return out","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:31.923260Z","iopub.execute_input":"2024-09-23T02:44:31.923606Z","iopub.status.idle":"2024-09-23T02:44:31.933790Z","shell.execute_reply.started":"2024-09-23T02:44:31.923569Z","shell.execute_reply":"2024-09-23T02:44:31.932875Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Residual Block based Visual Encoder\nThis encoder will extract spatial information.","metadata":{}},{"cell_type":"code","source":"class VisualEncoder(nn.Module):\n    def __init__(self, in_channels, out_channels, stride = 1, downsample = None):\n        super(VisualEncoder, self).__init__()\n        self.conv1 = nn.Sequential(\n                        nn.Conv2d(in_channels, out_channels, kernel_size = 3, stride = 2, padding = 3),\n                        nn.BatchNorm2d(out_channels),\n                        nn.ReLU()\n        )\n        self.conv2_x = nn.Sequential(\n                        ResidualBlock(out_channels, out_channels*4),\n                        nn.BatchNorm2d(out_channels*4),\n                        nn.ReLU(),\n                        nn.MaxPool2d(kernel_size=3, stride=2, padding=1),\n                        ResidualBlock(out_channels*4, out_channels*4),\n                        nn.Dropout(0.1)\n        )\n        \n        self.last = nn.Sequential(\n            nn.Conv2d(out_channels*4, out_channels*4, kernel_size=3, stride=2, padding=1, bias=False),\n            nn.BatchNorm2d(out_channels*4),\n            nn.ReLU(),\n        )        \n        \n    def forward(self, x):\n        out = self.conv1(x)\n        out = self.conv2_x(out)\n        out = self.last(out)\n        return out","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:31.935176Z","iopub.execute_input":"2024-09-23T02:44:31.936002Z","iopub.status.idle":"2024-09-23T02:44:31.945461Z","shell.execute_reply.started":"2024-09-23T02:44:31.935954Z","shell.execute_reply":"2024-09-23T02:44:31.944395Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Main model: DigitRecognizer","metadata":{}},{"cell_type":"code","source":"class FocalLoss(nn.Module):\n    def __init__(self, gamma=2.0, alpha=None, reduction='mean'):\n        super(FocalLoss, self).__init__()\n        self.gamma = gamma\n        self.alpha = alpha  # Can be set to a tensor of class weights\n        self.reduction = reduction\n\n    def forward(self, inputs, targets):\n        logp = F.log_softmax(inputs, dim=1)\n        p = torch.exp(logp)\n        logpt = logp.gather(1, targets.unsqueeze(1)).squeeze(1)\n        pt = p.gather(1, targets.unsqueeze(1)).squeeze(1)\n        if self.alpha is not None:\n            at = self.alpha.gather(0, targets)\n            logpt = logpt * at\n        loss = -((1 - pt) ** self.gamma) * logpt\n        if self.reduction == 'mean':\n            return loss.mean()\n        else:\n            return loss.sum()","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:31.946681Z","iopub.execute_input":"2024-09-23T02:44:31.947057Z","iopub.status.idle":"2024-09-23T02:44:31.959606Z","shell.execute_reply.started":"2024-09-23T02:44:31.947022Z","shell.execute_reply":"2024-09-23T02:44:31.958692Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Main model: DigitRecognizer","metadata":{}},{"cell_type":"code","source":"class DigitRecognizer(L.LightningModule):\n    def __init__(self, n_class, learning_rate):\n        super().__init__()\n        self.n_class = n_class\n        self.learning_rate = learning_rate\n        \n        self.model = VisualEncoder(1, 16)\n        self.encoder_outdim = 1024\n        self.fc = nn.Sequential(\n            nn.Flatten(),\n            nn.Linear(self.encoder_outdim, self.n_class)\n        )\n        \n        self.focal_loss = FocalLoss(gamma=2.0)\n        self.save_hyperparameters()\n        \n        # Lists for plotting training / validation results\n        self.train_loss_list, self.val_loss_list, self.train_acc_list, self.val_acc_list = [], [], [], []\n        # Dictionary for submission\n        self.test_preds = {\n            'ImageId' : [],\n            'Label' : []\n        }\n        \n        self.apply(self.weights_init_he)\n    \n    def weights_init_he(self, m):\n        if m == list(self.modules())[-1]:\n            return\n        if isinstance(m, (nn.Conv2d, nn.Conv3d)):\n            init.kaiming_normal_(m.weight, nonlinearity='relu')\n            if m.bias is not None:\n                init.constant_(m.bias, 0)\n        if isinstance(m, (nn.Linear)):\n            init.kaiming_normal_(m.weight, nonlinearity='linear')\n            if m.bias is not None:\n                init.constant_(m.bias, 0)\n    \n    def forward(self, imgs):\n        out = self.model(imgs)\n        logit_preds = self.fc(out)\n        return logit_preds\n    \n    def training_step(self, batch, batch_idx):\n        imgs, logit_targets = batch\n        logit_preds = self.forward(imgs)\n        preds, targets = torch.argmax(logit_preds, dim=-1), torch.argmax(logit_targets, dim=-1)\n        train_loss = self.focal_loss(logit_preds, targets)\n        train_acc = (preds == targets).float().mean()\n        \n        training_log = {\n            'train_loss': train_loss,\n            'train_acc': train_acc,\n        }\n        self.train_loss_list.append(train_loss.item())\n        self.train_acc_list.append(train_acc.item())\n        self.log_dict(training_log, prog_bar=True)\n        \n        return train_loss\n    \n    def validation_step(self, batch, batch_idx):\n        imgs, logit_targets = batch\n        logit_preds = self.forward(imgs)\n        preds, targets = torch.argmax(logit_preds, dim=-1), torch.argmax(logit_targets, dim=-1)\n        val_loss = self.focal_loss(logit_preds, targets)\n        val_acc = (preds == targets).float().mean()\n        \n        validation_log = {\n            'val_loss': val_loss,\n            'val_acc': val_acc,\n        }\n        self.val_loss_list.append(val_loss.item())\n        self.val_acc_list.append(val_acc.item())\n        self.log_dict(validation_log, prog_bar=True)\n    \n    def test_step(self, batch, batch_idx):\n        imgs, logit_targets = batch\n        logit_preds = self.forward(imgs)\n        logit_preds = logit_preds.cpu().detach()\n        label_pred = torch.argmax(logit_preds, dim=-1)[0]\n        self.test_preds['ImageId'].append(batch_idx+1)\n        self.test_preds['Label'].append(label_pred.item())\n        \n        \n    def configure_optimizers(self):\n        optimizer = torch.optim.AdamW(self.parameters(), lr=self.learning_rate, weight_decay=1e-4)\n        lr_scheduler = {\n            'scheduler': torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[10, 20, 60, 100], gamma=0.5),\n            'name': 'adam_lr_scheduler'\n        }\n        return [optimizer], [lr_scheduler]","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:31.961020Z","iopub.execute_input":"2024-09-23T02:44:31.961456Z","iopub.status.idle":"2024-09-23T02:44:31.982777Z","shell.execute_reply.started":"2024-09-23T02:44:31.961420Z","shell.execute_reply":"2024-09-23T02:44:31.981549Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Check the DigitRecognizer with a random input","metadata":{}},{"cell_type":"code","source":"device = 'cuda:0' if torch.cuda.is_available() else 'cpu'\nrgb = torch.randn(64, 1, 28, 28).to(device)\nmodel = DigitRecognizer(10, 0.0001).to(device)\n\n# Get number of parameters in M\npytorch_total_params = sum(p.numel() for p in model.parameters())\nprint(f\"Number of parameters: {pytorch_total_params/1e6:.2f}M\")\n\ncls_preds = model(rgb)\n\nprint(cls_preds.shape)","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:31.984275Z","iopub.execute_input":"2024-09-23T02:44:31.984677Z","iopub.status.idle":"2024-09-23T02:44:32.016567Z","shell.execute_reply.started":"2024-09-23T02:44:31.984632Z","shell.execute_reply":"2024-09-23T02:44:32.015647Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Training and Validation Pipelines","metadata":{}},{"cell_type":"code","source":"max_epochs = 150\nbatch_size = 8\nn_workers = 32\nn_class = 10\nlearning_rate = 1e-3\ndevice = 'cuda:0' if torch.cuda.is_available() else 'cpu'\n\n# Apply the datasets on our custom data loader\ntrain_loader = MNIST_loader('train', True, batch_size=batch_size, n_workers=n_workers, shuffle=True).get_data_loader()\nval_loader = MNIST_loader('val', False, batch_size=batch_size, n_workers=n_workers, shuffle=False).get_data_loader()\n\n\n# Load the main model\nmodel = DigitRecognizer(n_class, learning_rate)\n\n# Define checkpoint callback\ncheckpoint_callback = ModelCheckpoint(\n    monitor='val_loss',\n    dirpath='/kaggle/working',\n    filename=f'DR-{{epoch:04d}}-{{val_loss:.6f}}',\n    save_top_k=1,\n    mode='min',\n)\n\ntrainer = Trainer(\n    min_epochs=30,\n    max_epochs=max_epochs,\n    precision=\"bf16\",\n    accelerator=\"gpu\",\n    devices=1,\n    callbacks=[checkpoint_callback]\n)\ntrainer.fit(model, train_loader, val_loader)","metadata":{"execution":{"iopub.status.busy":"2024-09-23T02:44:32.017783Z","iopub.execute_input":"2024-09-23T02:44:32.018119Z","iopub.status.idle":"2024-09-23T06:21:30.596761Z","shell.execute_reply.started":"2024-09-23T02:44:32.018084Z","shell.execute_reply":"2024-09-23T06:21:30.595928Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Plot the results","metadata":{}},{"cell_type":"code","source":"train_losses, val_losses = model.train_loss_list, model.val_loss_list\ntrain_accs, val_accs = model.train_acc_list, model.val_acc_list\nsteps = range(len(train_losses))\nplt.figure()\nplt.title('Graph of Train Loss')\nplt.plot(steps, train_losses, label='Train Loss')\nplt.legend()\nplt.xlabel('Steps')\nplt.ylabel('Loss')\nplt.show()\n\nplt.figure()\nplt.title('Graph of Train Acc')\nplt.plot(steps, train_accs, label='Train Acc')\nplt.legend()\nplt.xlabel('Steps')\nplt.ylabel('Acc')\nplt.show()\n\nsteps = range(len(val_losses))\nplt.figure()\nplt.title('Graph of Val Loss')\nplt.plot(steps, val_losses, label='Val Loss')\nplt.legend()\nplt.xlabel('Steps')\nplt.ylabel('Loss')\nplt.show()\n\nplt.figure()\nplt.title('Graph of Val Acc')\nplt.plot(steps, val_accs, label='Val Acc')\nplt.legend()\nplt.xlabel('Steps')\nplt.ylabel('Acc')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-23T06:21:30.598295Z","iopub.execute_input":"2024-09-23T06:21:30.598633Z","iopub.status.idle":"2024-09-23T06:21:33.918568Z","shell.execute_reply.started":"2024-09-23T06:21:30.598595Z","shell.execute_reply":"2024-09-23T06:21:33.917629Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Run Testing","metadata":{}},{"cell_type":"code","source":"n_workers = 16\nn_class = 10\nlearning_rate = 1e-6\ntest_loader = MNIST_loader('test', False, batch_size=1, n_workers=n_workers, shuffle=False).get_data_loader()\n\ntrained_models = glob.glob('/kaggle/working/*.ckpt')\nbest_model = None\nmin_val = 1e6\nfor m in trained_models:\n    ckpt_name = m.rsplit('.', 1)[0]\n    val_loss = float(ckpt_name.split('/')[-1].split('-val_loss=')[-1])\n    if val_loss < min_val:\n        min_val = val_loss\n        best_model = m\nprint(f'The best model is {best_model}')\n    \n# Load models:\nmodel = DigitRecognizer.load_from_checkpoint(\n    checkpoint_path=best_model,\n    n_class=n_class,\n    learning_rate=learning_rate\n).cuda()\ntrainer = Trainer(\n    min_epochs=1,\n    max_epochs=1,\n    precision=\"bf16\",\n    accelerator=\"gpu\",\n    devices=1\n)\ntrainer.test(model, test_loader)","metadata":{"execution":{"iopub.status.busy":"2024-09-23T06:21:33.920028Z","iopub.execute_input":"2024-09-23T06:21:33.920414Z","iopub.status.idle":"2024-09-23T06:24:10.306524Z","shell.execute_reply.started":"2024-09-23T06:21:33.920368Z","shell.execute_reply":"2024-09-23T06:24:10.305483Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Save results and submit","metadata":{}},{"cell_type":"code","source":"test_data = model.test_preds","metadata":{"execution":{"iopub.status.busy":"2024-09-23T06:24:10.308104Z","iopub.execute_input":"2024-09-23T06:24:10.308449Z","iopub.status.idle":"2024-09-23T06:24:10.313025Z","shell.execute_reply.started":"2024-09-23T06:24:10.308413Z","shell.execute_reply":"2024-09-23T06:24:10.312053Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"output = pd.DataFrame(test_data)","metadata":{"execution":{"iopub.status.busy":"2024-09-23T06:24:10.314292Z","iopub.execute_input":"2024-09-23T06:24:10.314652Z","iopub.status.idle":"2024-09-23T06:24:10.351884Z","shell.execute_reply.started":"2024-09-23T06:24:10.314609Z","shell.execute_reply":"2024-09-23T06:24:10.350870Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"output.head(10)","metadata":{"execution":{"iopub.status.busy":"2024-09-23T06:24:10.353199Z","iopub.execute_input":"2024-09-23T06:24:10.353514Z","iopub.status.idle":"2024-09-23T06:24:10.369182Z","shell.execute_reply.started":"2024-09-23T06:24:10.353481Z","shell.execute_reply":"2024-09-23T06:24:10.368195Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"output.to_csv('submission.csv', index=False, sep=',')","metadata":{"execution":{"iopub.status.busy":"2024-09-23T06:24:10.370374Z","iopub.execute_input":"2024-09-23T06:24:10.370789Z","iopub.status.idle":"2024-09-23T06:24:10.415450Z","shell.execute_reply.started":"2024-09-23T06:24:10.370746Z","shell.execute_reply":"2024-09-23T06:24:10.414626Z"},"trusted":true},"execution_count":null,"outputs":[]}]}