{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Introduction\n\nThis Notebook is adapted from: [PyTorch: GPUx2 EfficientNet fine-tune](https://www.kaggle.com/code/tossimmar/pytorch-gpux2-efficientnet-fine-tune/) Notebook of [@tossimmar](https://www.kaggle.com/tossimmar).\n\nMain change is that we are using a Kaggle predefined model: EfficientNet\n","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","papermill":{"duration":11.542076,"end_time":"2023-03-06T18:32:47.402019","exception":false,"start_time":"2023-03-06T18:32:35.859943","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2023-03-15T17:08:49.323749Z","iopub.execute_input":"2023-03-15T17:08:49.324693Z","iopub.status.idle":"2023-03-15T17:09:00.978414Z","shell.execute_reply.started":"2023-03-15T17:08:49.324638Z","shell.execute_reply":"2023-03-15T17:09:00.977234Z"}}},{"cell_type":"markdown","source":"# Imported packages","metadata":{}},{"cell_type":"code","source":"import io\nimport glob\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nimport seaborn as sns; sns.set()\nfrom sklearn.metrics import f1_score\n\nimport tensorflow as tf\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torchvision.models as models\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.optim.lr_scheduler import CosineAnnealingLR\nfrom torchvision.transforms import Compose, Lambda, ToTensor, Normalize, Resize, RandomCrop, TenCrop, RandomHorizontalFlip","metadata":{"execution":{"iopub.status.busy":"2023-05-09T19:28:21.791398Z","iopub.execute_input":"2023-05-09T19:28:21.792230Z","iopub.status.idle":"2023-05-09T19:28:37.432015Z","shell.execute_reply.started":"2023-05-09T19:28:21.792179Z","shell.execute_reply":"2023-05-09T19:28:37.430804Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Set configuration","metadata":{}},{"cell_type":"code","source":"cfg = {\n     \"train_files\": '/kaggle/input/tpu-getting-started/tfrecords-jpeg-224x224/train/*.tfrec',\n     \"valid_files\": '/kaggle/input/tpu-getting-started/tfrecords-jpeg-224x224/val/*.tfrec',\n     \"test_files\": '/kaggle/input/tpu-getting-started/tfrecords-jpeg-224x224/test/*.tfrec',\n     'model': 'tf_efficientnet_b0',\n     'checkpoint_path': '/kaggle/input/tf-efficientnet/pytorch/tf-efficientnet-b0/1/tf_efficientnet_b0_aa-827b6e33.pth',\n     \"device\": torch.device('cuda' if torch.cuda.is_available() else 'cpu'), # hardware\n     \"n_classes\": 104,\n     \"n_epochs\": 50,                                                            # number of training epochs\n     \"batch_size\": 20,                                                           # training batch size\n     \"num_prints\": 10,                                                            # number of losses to print per epoch\n     \"train_size\": 12753,                                                        # number of training data samples\n     \"check_freq\": 1,                                                            # save model if epoch is a multiple of this\n}\ncfg[\"print_freq\"] = cfg[\"train_size\"] // (cfg[\"batch_size\"] * cfg[\"num_prints\"]) + 1   #how often to print out update on training evolution","metadata":{"execution":{"iopub.status.busy":"2023-05-09T19:28:37.434413Z","iopub.execute_input":"2023-05-09T19:28:37.435261Z","iopub.status.idle":"2023-05-09T19:28:37.543097Z","shell.execute_reply.started":"2023-05-09T19:28:37.435222Z","shell.execute_reply":"2023-05-09T19:28:37.541478Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's check the configuration.","metadata":{}},{"cell_type":"code","source":"cfg","metadata":{"execution":{"iopub.status.busy":"2023-05-09T19:28:37.545030Z","iopub.execute_input":"2023-05-09T19:28:37.545845Z","iopub.status.idle":"2023-05-09T19:28:37.564398Z","shell.execute_reply.started":"2023-05-09T19:28:37.545794Z","shell.execute_reply":"2023-05-09T19:28:37.563228Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Utility functions\n\n\nHere we define two utility functions:\n* for converting tfrecords to dataframe, so that we will use easier with our model  \n* to visualize the images","metadata":{}},{"cell_type":"code","source":"# Utility functions:\n# ------------------\n\ndef tfrecords_to_dataframe(fp, test = False):\n    '''\n    Parse data files into rows of a dataframe.\n    \n    arguments\n    ---------\n    fp : str\n        Data files pattern.\n        \n    test : bool\n        If true, data files correspond to testing data.\n    '''\n    def parse(pb, test = False):\n        d = {'id': tf.io.FixedLenFeature([], tf.string), 'image': tf.io.FixedLenFeature([], tf.string)}\n        if not test:\n            d['class'] = tf.io.FixedLenFeature([], tf.int64)\n        return tf.io.parse_single_example(pb, d)\n\n    df = {'id': [], 'img': []} \n    if not test:\n        df['lab'] = []\n    for sample in tf.data.TFRecordDataset(glob.glob(fp)).map(lambda pb: parse(pb, test)):\n        df['id'].append(sample['id'].numpy().decode('utf-8'))\n        df['img'].append(sample['image'].numpy())\n        if not test:\n            df['lab'].append(sample['class'].numpy())\n    return pd.DataFrame(df)\n\n# ------------------------------------------------------------------------------------------------------------------------\n\ndef display_images(dataset, n, cols):\n    '''\n    Display a grid of labelled images of flowers.\n    \n    arguments\n    ---------\n    dataset : Dataset\n        Dataset containing the flower images and labels.\n        \n    n : int\n        Number of images to display.\n        \n    cols : int\n        Number of columns in the grid.\n    '''\n    rows = n // cols if n % cols == 0 else n // cols + 1\n    plt.figure(figsize = (2 * cols, 2 * rows))\n    for i in range(n):\n        plt.subplot(rows, cols, i + 1)\n        img, lab = dataset[i]\n        plt.imshow(img.permute(1, 2, 0).numpy())\n        plt.title(str(lab))\n        plt.axis('off')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2023-05-09T19:28:37.568713Z","iopub.execute_input":"2023-05-09T19:28:37.569278Z","iopub.status.idle":"2023-05-09T19:28:37.586021Z","shell.execute_reply.started":"2023-05-09T19:28:37.569237Z","shell.execute_reply":"2023-05-09T19:28:37.584424Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Define the classes for train & valid set\n\n\nWe define now the two  classes for train and valid/test set:\n* Trainest - for the training set\n* Evalset - for evaluation (validation or test) set   \n","metadata":{}},{"cell_type":"code","source":"class Trainset(Dataset):\n    '''\n    Representation of the training dataset.\n    '''\n    def __init__(self, frac = 1):\n        '''\n        arguments\n        ---------\n        frac : float\n            Fraction of data samples to keep.\n            \n            For example, if frac = 0.5, then a random sample of 50% \n            of the data is kept and the remaining 50% is discarded.\n        '''\n        super().__init__()\n        self.df = tfrecords_to_dataframe(cfg[\"train_files\"]).sample(frac = frac).reset_index(drop = True)\n        self.t1 = Lambda(lambda b: Image.open(io.BytesIO(b)))\n        self.t2 = Compose([RandomCrop(300), \n                           RandomHorizontalFlip(), \n                           ToTensor(), \n                           Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])\n        \n    def __len__(self):\n        \"\"\"\n            Returns \n                the length of the dataframe with images\n        \"\"\"\n        return self.df.shape[0]\n    \n    def __getitem__(self, i):\n        \"\"\"\n        Returns the current item (image + label) from trainset\n        Will resize it to 300 x 641 before\n            Args\n                i: index of current item to return\n            Returns\n        \"\"\"\n        transform = Compose([self.t1, Resize(np.random.randint(300, 641)), self.t2])\n        sample = self.df.iloc[i]\n        return transform(sample['img']), sample['lab']\n\n\nclass Evalset(Dataset):\n    '''\n    Representation of the evaluation datasets.\n    '''\n    def __init__(self, frac = 1, test = False):\n        '''\n        Args\n            frac : float Fraction of data samples to keep.\n            \n            For example, if frac = 0.5, then a random sample of 50% \n            of the data is kept and the remaining 50% is discarded.\n            \n            test : bool\n                If true, this dataset contains the testing data. \n                Otherwise, this dataset contains the validation data. \n        Returns\n            none\n        '''\n        super().__init__()\n        files = cfg[\"valid_files\"] if not test else cfg[\"test_files\"]\n        self.df = tfrecords_to_dataframe(files, test).sample(frac = frac).reset_index(drop = True)\n        self.transforms = [Compose([Lambda(lambda b: Image.open(io.BytesIO(b))), \n                                    Resize(scale), \n                                    TenCrop(300), \n                                    Lambda(lambda xs: torch.stack([ToTensor()(x) for x in xs])), \n                                    Lambda(lambda xs: torch.stack([Normalize([0.485, 0.456, 0.406], \n                                                                             [0.229, 0.224, 0.225])(x) for x in xs]))])\n                           for scale in [372, 568]]\n        self.test = test\n        \n    def __len__(self):\n        \"\"\"\n        Returns\n            the valid dataset length\n        \"\"\"\n        return self.df.shape[0]\n    \n    def __getitem__(self, i):\n        \"\"\"\n        Generic evaluation class\n        Can be used either for validation set or test set\n        Args\n            i: current item\n        Returns \n            either the image and the label (if class is used for validation set) or\n            the image only (if class is used for test set)\n        \"\"\"\n        sample = self.df.iloc[i]\n        imgs = torch.stack([t(sample['img']) for t in self.transforms])\n        return imgs, sample['lab'] if not self.test else sample['id']\n","metadata":{"nteract":{"transient":{"deleting":false}},"execution":{"iopub.status.busy":"2023-05-09T19:28:37.590240Z","iopub.execute_input":"2023-05-09T19:28:37.590937Z","iopub.status.idle":"2023-05-09T19:28:37.612132Z","shell.execute_reply.started":"2023-05-09T19:28:37.590883Z","shell.execute_reply":"2023-05-09T19:28:37.610554Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model class\n\nThis is the class used to fine-tune EfficientNetB0","metadata":{}},{"cell_type":"code","source":"import timm\n\nclass EfficientNetB0(nn.Module):\n    '''\n    EfficientNet B0 fine-tune.\n    '''\n    def __init__(self, n_classes, learnable_modules = ('classifier',)):\n        '''\n        Fine tune for EfficientNetB0\n        Args\n            n_classes : int - Number of classification categories.\n            learnable_modules : tuple - Names of the modules to fine-tune.\n        Return\n            \n        '''\n        super().__init__()\n        \n        model = timm.create_model(cfg['model'], \n                                  checkpoint_path=cfg['checkpoint_path'])\n        self.efficientnet_b0 = model\n        self.efficientnet_b0.classifier = nn.Linear(self.efficientnet_b0.classifier.in_features, n_classes)\n        self.efficientnet_b0.requires_grad_(False)\n        modules = dict(self.efficientnet_b0.named_modules())\n        for name in learnable_modules:\n            modules[name].requires_grad_(True)\n        \n    def forward(self, x):\n        \"\"\"\n        Forward function for the fine-tuned model\n        Args\n            x: \n        Return\n            result\n        \"\"\"\n        return F.log_softmax(self.efficientnet_b0(x), dim = 1)","metadata":{"execution":{"iopub.status.busy":"2023-05-09T19:28:37.613618Z","iopub.execute_input":"2023-05-09T19:28:37.615328Z","iopub.status.idle":"2023-05-09T19:28:38.627009Z","shell.execute_reply.started":"2023-05-09T19:28:37.615284Z","shell.execute_reply":"2023-05-09T19:28:38.625694Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Prepare train, valid and test data","metadata":{}},{"cell_type":"code","source":"# Training, validation, and testing data:\n# ---------------------------------------\ntrain_set    = Trainset()\ntrain_loader = DataLoader(train_set, batch_size = cfg[\"batch_size\"], shuffle = True, num_workers = 2)\nvalid_loader = DataLoader(Evalset(frac = 0.20), batch_size = 1, num_workers = 2)\ntest_loader  = DataLoader(Evalset(test = True), batch_size = 1, num_workers = 2)","metadata":{"papermill":{"duration":25.462451,"end_time":"2023-03-06T18:33:12.910134","exception":false,"start_time":"2023-03-06T18:32:47.447683","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2023-05-09T19:28:38.628936Z","iopub.execute_input":"2023-05-09T19:28:38.629843Z","iopub.status.idle":"2023-05-09T19:29:05.832714Z","shell.execute_reply.started":"2023-05-09T19:28:38.629789Z","shell.execute_reply":"2023-05-09T19:29:05.831436Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Display sample images (with labels)","metadata":{}},{"cell_type":"markdown","source":"<div class=\"alert alert-info\">Display some training images and labels</div>","metadata":{"execution":{"iopub.status.busy":"2023-03-18T10:31:05.691577Z","iopub.execute_input":"2023-03-18T10:31:05.692161Z","iopub.status.idle":"2023-03-18T10:31:05.698855Z","shell.execute_reply.started":"2023-03-18T10:31:05.692125Z","shell.execute_reply":"2023-03-18T10:31:05.697337Z"}}},{"cell_type":"code","source":"display_images(train_set, n = 40, cols = 8)","metadata":{"papermill":{"duration":3.649506,"end_time":"2023-03-06T18:33:16.566804","exception":false,"start_time":"2023-03-06T18:33:12.917298","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2023-05-09T19:29:05.834336Z","iopub.execute_input":"2023-05-09T19:29:05.834745Z","iopub.status.idle":"2023-05-09T19:29:10.152776Z","shell.execute_reply.started":"2023-05-09T19:29:05.834697Z","shell.execute_reply":"2023-05-09T19:29:10.151231Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Define the optimizer","metadata":{}},{"cell_type":"markdown","source":"<div class=\"alert alert-success\">\nIn the optimizer below, we define five parameter groups (really two as the first four can be combined). \n\nThe first four parameter groups consist of parameters pre-trained on ImageNet and the last parameter group consists of the final affine layer parameters which are trained from scratch. \n\nWe set the pre-trained parameters' learning rate to 0.0001 and the \"from scratch\" parameters' learning rate to 0.001 -- that is, pre-trained parameters are fine-tuned using a learning rate which is an order of magnitude less than the learning rate of the from scratch parameters. \n\nThe learning rate scheduler decays the learning rates of all groups to zero over the course of training.</div>","metadata":{}},{"cell_type":"code","source":"# Modelling components:\n# ---------------------\nmodel = nn.DataParallel(EfficientNetB0(n_classes = cfg[\"n_classes\"], \n                                       learnable_modules = (['classifier'])))\nmodel.to(cfg[\"device\"])\n\noptimizer = torch.optim.Adam(params = [\n                                       {'params': model.module.efficientnet_b0.classifier.parameters(), 'lr': 2.e-4}], \n                             lr = 2e-5, \n                             weight_decay = 1e-4)\n\nscheduler = CosineAnnealingLR(optimizer, T_max = cfg[\"n_epochs\"])\n\nloss_fn = F.nll_loss","metadata":{"papermill":{"duration":0.722297,"end_time":"2023-03-06T18:33:17.344","exception":false,"start_time":"2023-03-06T18:33:16.621703","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2023-05-09T19:29:10.154028Z","iopub.execute_input":"2023-05-09T19:29:10.154468Z","iopub.status.idle":"2023-05-09T19:29:10.877201Z","shell.execute_reply.started":"2023-05-09T19:29:10.154430Z","shell.execute_reply":"2023-05-09T19:29:10.876039Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training loop","metadata":{}},{"cell_type":"markdown","source":"Define train and validate functions.","metadata":{}},{"cell_type":"code","source":"def train(train_losses, epoch):\n    print()\n    print(f'Epoch {epoch}:')\n    print('-' * len(f'Epoch {epoch}:'))\n    model.train() \n    for i, data in enumerate(train_loader):\n        image, label = data\n        image = image.to(cfg[\"device\"])\n        label = label.to(cfg[\"device\"])\n        # forward pass\n        outputs = model(image)\n        # calculate the loss\n        loss = loss_fn(outputs, label)\n        optimizer.zero_grad()\n        # backpropagation\n        loss.backward()\n        # optimize the weights\n        optimizer.step()\n        \n        # optionaly, print the loss\n        if i % cfg[\"print_freq\"] == 0:\n            print('Loss {}: {:.3f}'.format(i, loss.item()))\n            train_losses.append(loss.item())\n\n\ndef validate(valid_f1s):\n    model.eval()\n    valid_true_labels = []\n    valid_pred_labels = []\n    valid_running_loss = []\n    counter = 0\n    with torch.no_grad():\n        for data in valid_loader:\n            counter += 1\n            image, labels = data\n            # append true labels\n            valid_true_labels.append(labels.item())\n\n            image = image.view(-1, 3, 300, 300).to(cfg[\"device\"])\n            labels = labels.to(cfg[\"device\"])\n            # forward pass\n            outputs = model(image)\n            # calculate the accuracy\n            mean_logp = outputs.mean(dim = 0)\n            preds = torch.argmax(mean_logp).item()\n            valid_pred_labels.append(preds)\n\n    valid_f1 = f1_score(valid_true_labels, valid_pred_labels, average = 'weighted')\n    valid_f1s.append(valid_f1)\n    \n    print()\n    print('Validation F1: {:.2f}%'.format(valid_f1 * 100))\n    torch.save(model.state_dict(), f'./epoch{epoch // cfg[\"check_freq\"]}.pth')","metadata":{"execution":{"iopub.status.busy":"2023-05-09T19:29:10.880616Z","iopub.execute_input":"2023-05-09T19:29:10.880954Z","iopub.status.idle":"2023-05-09T19:29:10.895329Z","shell.execute_reply.started":"2023-05-09T19:29:10.880921Z","shell.execute_reply":"2023-05-09T19:29:10.894050Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Training loop:\n# --------------\ntrain_losses = []  # store train losses at each epoch       \nvalid_f1s = []  # store f1-score for validation at each epoch                                                          \nfor epoch in range(cfg[\"n_epochs\"]):\n    train(train_losses, epoch)\n    # evaluate model (with check_freq)\n    if epoch % cfg[\"check_freq\"] == 0:\n        validate(valid_f1s)\n    scheduler.step()","metadata":{"papermill":{"duration":3470.218849,"end_time":"2023-03-06T19:31:07.577597","exception":false,"start_time":"2023-03-06T18:33:17.358748","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2023-05-09T19:29:19.931432Z","iopub.execute_input":"2023-05-09T19:29:19.931865Z","iopub.status.idle":"2023-05-09T19:34:27.161961Z","shell.execute_reply.started":"2023-05-09T19:29:19.931824Z","shell.execute_reply":"2023-05-09T19:34:27.157238Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Get the optimum epoch","metadata":{}},{"cell_type":"code","source":"optimal_epoch = np.argmax(np.array(valid_f1s)) # highest validation F1 epoch / checkpoint frequency","metadata":{"papermill":{"duration":0.036756,"end_time":"2023-03-06T19:31:07.641305","exception":false,"start_time":"2023-03-06T19:31:07.604549","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2023-03-18T11:56:17.854271Z","iopub.execute_input":"2023-03-18T11:56:17.855058Z","iopub.status.idle":"2023-03-18T11:56:17.860704Z","shell.execute_reply.started":"2023-03-18T11:56:17.855021Z","shell.execute_reply":"2023-03-18T11:56:17.859424Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Validation","metadata":{}},{"cell_type":"markdown","source":"<div class=\"alert alert-success\">\n    Plot training loss and validation F1\n    </div>","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize = (12, 4))\nplt.subplot(1, 2, 1)\nplt.plot(np.arange(len(train_losses)) / cfg[\"n_epochs\"], train_losses, linewidth = 1)\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Training Losses')\nplt.subplot(1, 2, 2)\nplt.plot(np.arange(len(valid_f1s)) * cfg[\"check_freq\"], valid_f1s, linewidth = 1)\nplt.vlines(optimal_epoch * cfg[\"check_freq\"], 0, valid_f1s[optimal_epoch], colors = 'black', linestyles = 'dashed', label = f'Optimal epoch ({optimal_epoch * cfg[\"check_freq\"]})')\nplt.xlabel('Epoch')\nplt.ylabel('Weighted F1')\nplt.ylim(0, 1)\nplt.title('Validation')\nplt.legend(loc = 'lower left')\nplt.savefig('plot.png')\nplt.show()","metadata":{"papermill":{"duration":0.69134,"end_time":"2023-03-06T19:31:08.358922","exception":false,"start_time":"2023-03-06T19:31:07.667582","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2023-03-18T11:56:20.008209Z","iopub.execute_input":"2023-03-18T11:56:20.008671Z","iopub.status.idle":"2023-03-18T11:56:20.712133Z","shell.execute_reply.started":"2023-03-18T11:56:20.008629Z","shell.execute_reply":"2023-03-18T11:56:20.705573Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Use the best model\n\nThe model that achievel largest validation F1 is loaded and used for submission preparation.","metadata":{}},{"cell_type":"code","source":"# Load the model which achieved the largest validation F1:\n# --------------------------------------------------------\nmodel = nn.DataParallel(EfficientNetB0(n_classes = cfg[\"n_classes\"], learnable_modules = ())).to(cfg[\"device\"])\nmodel.load_state_dict(torch.load(f'./epoch{optimal_epoch}.pth'))","metadata":{"papermill":{"duration":0.29888,"end_time":"2023-03-06T19:31:08.682134","exception":false,"start_time":"2023-03-06T19:31:08.383254","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2023-03-17T06:27:13.041039Z","iopub.execute_input":"2023-03-17T06:27:13.041437Z","iopub.status.idle":"2023-03-17T06:27:13.335822Z","shell.execute_reply.started":"2023-03-17T06:27:13.041404Z","shell.execute_reply":"2023-03-17T06:27:13.334616Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Submission","metadata":{}},{"cell_type":"code","source":"# Submission:\n# -----------\nids = []\npreds = []\nmodel.eval()\nwith torch.no_grad():\n    for x, y in test_loader:\n        ids.append(y[0])\n        mean_logp = model(x.view(-1, 3, 300, 300).to(cfg[\"device\"])).mean(dim = 0)\n        preds.append(torch.argmax(mean_logp).item())\nsubmission = pd.DataFrame({'id': ids, 'label': preds})\nsubmission.to_csv('submission.csv', index = False)\nsubmission.head()","metadata":{"papermill":{"duration":825.091225,"end_time":"2023-03-06T19:44:53.796998","exception":false,"start_time":"2023-03-06T19:31:08.705773","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2023-03-17T06:27:15.35358Z","iopub.execute_input":"2023-03-17T06:27:15.353943Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Final note\n\nThis Notebook is 100% crafted by and [@tossimmar](https://www.kaggle.com/tossimmar).  \n\nI forked, performed minor changes and run for (my own) learning purposes.   \n\nI also switched to use Kaggle Model(s), namely EfficientNet b0\n\nAll credit for this work should go to [@tossimmar](https://www.kaggle.com/tossimmar).  ","metadata":{}}]}