{"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":"# These research-papers were used for this code:\n\n* SnapMix: Semantically Proportional Mixing for Augmenting Fine-grained Data - http://arxiv.org/abs/2012.04846\n\nAnd to understand the SnapMix article:\n* Mixup: Beyond Empirical Risk Minimization - http://arxiv.org/abs/1710.09412\n* Learning Deep Features for Discriminative Localization - http://arxiv.org/abs/1512.04150","metadata":{}},{"cell_type":"code","source":"# directories and paths to data:\n\nimport torch\nimport os\nimport pandas as pd\nfrom pathlib import Path\nimport numpy as np\n\nbase_path = Path('../input/cassava-leaf-disease-classification')\ntrain_img_dir = os.path.join(base_path,'train_images')\ntest_img_dir = os.path.join(base_path,'test_images')\n\ntrain_images = os.listdir(train_img_dir)\ntest_images = os.listdir(test_img_dir)\n\ntrain_df = pd.read_csv(os.path.join(base_path,'train.csv'))\n\ndiseaseMapping = pd.read_json(os.path.join(base_path,'label_num_to_disease_map.json'), typ='series')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# test if training data have been loaded:\n# train_df.shape","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **Plan:**\n- look up necessary preprocessing for resnet50 input: implement it:  ok\n- load a batch of 2 images to test the snapmix augmentation part: ok\n\nWhen that is ok:\n- write data-loader to read in data and label: ok\n- test for snapmix batch-loss-function: ok\n- write training-loop: ok\n- add classifier to network: ok\n- freeze extractor, train classifier: ok\n- implemented in pytorch save & load best performing model: ok\n- thaw last conv layer, train some more: ok - thawing last conv layer after 5 epochs.\n- submit code to Kaggle:\n- if better than 44% - upload to github:\n- finally: add midlayer information like described in the paper\n- if that is hopefully even better - upload to github:","metadata":{}},{"cell_type":"markdown","source":"# Data splitting, data-sets, data-loader here:","metadata":{}},{"cell_type":"code","source":"# use this to split data in train_df into TRAINING AND EVALUATION (but not an additional test set):\nfraction_training = 0.8\n\nassert fraction_training < 1, \"!fraction_training must be smaler then 1, else there will be no evaluation data!\"\n\neval_df = train_df.sample(frac=1-fraction_training)\ntrain_df = train_df.drop(eval_df.index)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# test/ controll for data splitting:\n\n#eval_df.shape, eval_df.shape[0]/len(index), train_df.shape, train_df.shape[0]/len(index)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# use this to split data in train_df into TRAINING, EVALUATION AND TEST SET:\n\n# choose splitting fractions:\n#fraction_training = 0.8\n#fraction_evaluation = 0.1\n#fraction_test = 0.1\n\n#assert fraction_training + fraction_evaluation + fraction_test == 1, \"fraction_training + fraction_evaluation + fraction_test must sum to 1\"\n\n#f = 1 - fraction_training\n#eval_df = train_df.sample(frac = f)\n#train_df = train_df.drop(eval_df.index)\n#test_df = eval_df.sample(frac= fraction_test/f)\n#eval_df = eval_df.drop(test_df.index)\n\n#eval_df.shape, test_df.shape, train_df.shape","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport pandas as pd\nfrom PIL import Image\nfrom torch.utils.data import Dataset\n\nclass CassavaImageDataset(Dataset):\n    \"\"\"\n    Reads image name and label from pandas data-frame, loads data via PIL, applies transform to image and label and return (image, label) pair.\n    \n    Args:\n        label_image_dataframe (pandas datafram): pandas data-frame containing image name and label\n        img_dir_path (string): path to the directory containing the images\n        transform: transform to be applied to the data/images\n        label_transform: transform to be applied to the labels\n    \n    Returns:\n        sample (dictionary): {\"image\": image, \"label\": label, \"img_name\" : image_name}\n    \"\"\"\n    def __init__(self, image_label_dataframe, img_dir_path, transform=None, label_transform=None):\n        self.image_label_df = image_label_dataframe\n        self.img_dir_path = img_dir_path\n        self.transform = transform\n        self.label_transform = label_transform\n\n    def __len__(self):\n        return len(self.image_label_df)\n\n    def __getitem__(self, idx):\n        image_name = self.image_label_df.iloc[idx, 0]\n        img_path = os.path.join(self.img_dir_path, image_name)\n        image = Image.open(img_path)\n        label = self.image_label_df.iloc[idx, 1]\n        if self.transform is not None:\n            image = self.transform(image)\n        if self.label_transform is not None:\n            label = self.label_transform(label)\n        sample = {\"image\": image, \"label\": label, \"img_name\" : image_name}\n        \n        return sample","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Construction of datasets and dataloaders","metadata":{}},{"cell_type":"code","source":"# construction of datasets and dataloader:\n\nfrom torchvision import transforms\n\n# necessary resnet50 preprocessor:\npreprocess_resnet50 = transforms.Compose([\n    transforms.Resize(256),\n    transforms.CenterCrop(224),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[4.485, 0.456, 0.406],\n        std= [0.229, 0.224, 0.225]),\n])\n\nfrom torch.utils.data import DataLoader\n\n# definition of dataloaders:\ntrain_ds = CassavaImageDataset(train_df, train_img_dir, transform=preprocess_resnet50)\ntrain_dl = DataLoader(train_ds, batch_size=32, shuffle=True)\n\neval_ds = CassavaImageDataset(eval_df, train_img_dir, transform=preprocess_resnet50)\neval_size = eval_df.shape[0]\neval_dl = DataLoader(eval_ds, batch_size=32, shuffle=False)\n\n# check if test_df has been created/ is desired - if so, create the dataloader test_dl for it:\nif \"test_df\" in locals():\n    test_ds = CassavaImageDataset(test_df, train_img_dir, transform=preprocess_resnet50)\n    test_size = test_df.shape[0]\n    test_dl = DataLoader(test_ds, batch_size=test_size, shuffle=False)\n    print(\"dataloader test_dl created.\")\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# test dataloader:\n\n#for i, sample in enumerate(train_dl):\n#    if i > 1:\n#       break\n#    print(sample[\"image\"].shape)\n#    print(sample[\"label\"].shape)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model definition: resnet50, featureNet, cassava-classifier here:","metadata":{}},{"cell_type":"code","source":"# DEFINITION OF THE MODEL(S):\n\nimport torch.nn as nn\nimport torchvision.models as models\n\nclass Cassava_resnet50(nn.Module):\n    def __init__(self, freeze_featureNet=True):\n        \"\"\"\n            freeze_featureNet (bool): if true sets requires_grad = False for all layers except the fc classifier layer.\n        \"\"\"\n        super(Cassava_resnet50, self).__init__()\n        #resnet50 = models.resnet50(pretrained=True) # needs internet connection\n        #--- without internet connection:\n        resnet50 = models.resnet50(pretrained=False)\n        dict_resnet50 = torch.load(\"../input/resnet50/resnet50.pth\")\n        resnet50.load_state_dict(dict_resnet50)\n        #---\n        resnet_no_classifier = list(resnet50.children())[:-2]\n        self.featureNet = nn.Sequential(*resnet_no_classifier)\n        self.avgPool = nn.AdaptiveAvgPool2d(output_size=(1,1))\n        self.cassava_fc = nn.Linear(2048, 5, bias=True)\n        \n        if freeze_featureNet:\n            for param in self.featureNet.parameters():\n                param.requires_grad = False\n        \n        \n    def forward(self, x):\n        \"\"\"\n        Returns: \n            two outputs: feature_model_output, classification_model_output\n        \"\"\"\n        x = self.featureNet(x)\n        y = self.avgPool(x)\n        y = torch.squeeze(y)\n        y = self.cassava_fc(y)\n        \n        return x,y\n\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# test model creation:\n\n#model = Cassava_resnet50()\n#print(model.cassava_fc.weight)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Functions for snapmix here:","metadata":{}},{"cell_type":"code","source":"# functions for snap-mix augmentation and loss:\n\nimport torchvision.transforms as T\nimport torchvision.transforms.functional as TF\nimport numpy as np\n\n\ndef snapmix_batch_loss(label_batch, y_scores, box_weights1 = None, box_weights2 = None):\n    \"\"\"\n    Calculates the loss according to snap-mix algorithm.\n    \n    Args:\n        label_batch : true labels\n        y_scores : raw-score vectors for label-prediction\n        box_weights1 : semantic box weights of patched-into images\n        box_weights2 : semantic box weights of patched-in images\n    \n    Returns:\n        snap-mix loss\n    \"\"\"\n    loss = torch.nn.CrossEntropyLoss()(y_scores, label_batch)\n    return torch.mean(torch.mul(loss, (1 - box_weights1 + box_weights2)), dim=0)\n\n\ndef snapmix_batch_augmentation(model, img_batch, label_batch, alpha=0.2):\n    \"\"\"\n    Applies, the SnapMix-augmentation to the images and labels within a data batch with respect to a model.\n\n    Args:\n        model (Cassava_resnet50) : the part of the model ending in the last feature map (convolution) of the resnet50\n        img_batch (torch.tensor) : batch with images, all the same shape\n        label_batch (numpy list) : batch with labels for the images\n        alpha (float), optional: parameter for beta-distribution generating image shrinking-factor for box-area\n\n    Returns:\n        augmented_images : the augmented input-images\n        label_batch2 : the labels of the images that have been patched into the input-images\n        box_weights1 : batch of semantic weights of cut-out-boxes\n        box_weights2 : batch of semantic weights of patched-in-boxes\n    \"\"\"\n    # pytorch uses: B x C x H x W:\n    input_batch_size = img_batch.shape[0]\n    input_img_height = img_batch.shape[2]\n    input_img_width = img_batch.shape[3]\n        \n    box1 = random_box(input_img_width, input_img_height, alpha=alpha)\n    box2 = random_box(input_img_width, input_img_height, alpha=alpha)\n    \n    # To increase speed we copy and permutate the images of the batch and patch the images from this\n    # new batch - so we have allready the semantic percentage map for the copied batch:\n\n    # build another image batch from the input batch:\n    permutation = torch.randperm(input_batch_size)\n    label_batch = label_batch.type(torch.int64)\n    img_batch2 = torch.clone(img_batch.detach())\n    img_batch2 = img_batch2[permutation]\n\n    # get spm and calculate boxweights:\n    SPM1 = batch_semantic_percentage_map(\n        model = model,\n        img_batch = img_batch,\n        label_batch = label_batch,\n    )\n    \n    # copy and permute the semantic percentage maps of the first batch in the same way as the\n    # images of img_batch2 :\n    #SPM2 = torch.clone(SPM1)\n    #SPM2 = SPM2[permutation, :, :]\n    \n    # crop boxes:\n    x11, y11, x12, y12 = box1\n    x21, y21, x22, y22 = box2\n    height_box1 = x12 - x11\n    width_box1 = y12 - y11\n    height_box2 = x22 - x21\n    width_box2 = y22 - y21\n    \n    cropped_SPM1 = TF.crop(SPM1, top=x11, left=y11, height=height_box1, width=width_box1)\n    box_weights1 = torch.sum(cropped_SPM1, dim=(1, 2))\n    #cropped_SPM2 = TF.crop(SPM2, top=x21, left=y21, height=height_box2, width=width_box2)\n    #box_weights2 = torch.sum(cropped_SPM2, dim=(1, 2))\n    #print(\"box_weights2 :\", box_weights2)\n    cropped_SPM2 = TF.crop(SPM1, top=x21, left=y21, height=height_box2, width=width_box2)\n    box_weights2 = torch.sum(cropped_SPM2, dim=(1, 2))\n    box_weights2 = box_weights2[permutation]\n    #print(\"box_weights12:\", box_weights12[permutation])\n\n    # fix for cases where box_weights are not well defined: we take the relative areas of the boxes:\n    rel_area1 = height_box1 * width_box1 /  (input_img_width * input_img_height)\n    rel_area2 = height_box2 * width_box2 / (input_img_width * input_img_height)\n    box_weights1[torch.isnan(box_weights1)] = rel_area1\n    box_weights2[torch.isnan(box_weights2)] = rel_area2\n\n    #crop and paste images:\n    cropped_img2s = TF.crop(img_batch2, top=x11, left=y11, height=height_box1, width=width_box1)\n    resized_cropped_img2s = T.Resize((height_box1, width_box1))(cropped_img2s)\n    img_batch[:, :, x11: x12, y11:y12] = resized_cropped_img2s\n\n    return img_batch, box_weights1, box_weights2\n\n\ndef batch_semantic_percentage_map(model, img_batch, label_batch):\n    \"\"\"\n    Calculates the SPM - Semantic Percentage Map of a batch of images.\n\n    Args:\n        model (Cassava_resnet50): \n        img_batch: batch of input images\n        label_batch: batch of the images labels\n\n    Returns:\n        the SPMs (Semantic Percentage Maps) for a batch of images.\n    \"\"\"\n    # weights for determining the contribution to the final classification:\n    # classing_weights.shape = [number of classes, number of fc-layer neurons], i.e. here [5,2048]\n    classing_weights = model.cassava_fc.weight\n    # the batch of all feature map batches for all images in the img_batch:\n    feature_maps_batch, _ = model(img_batch) \n\n    # Calculate Class Activation Map (CAM):\n    # for the numbers: feature_maps_batch.shape = [number of images, channels, height, width]\n    img_batch_size = feature_maps_batch.shape[0] \n    feature_map_height = feature_maps_batch.shape[2]\n    feature_map_width = feature_maps_batch.shape[3]\n    CAM_batch = torch.zeros((img_batch_size, feature_map_width, feature_map_height))\n\n    clw_batch_matrix = classing_weights[label_batch, :]\n    for i in range(img_batch_size):\n        class_weights = clw_batch_matrix[i,:].detach()\n        feature_map = feature_maps_batch[i,:,:,:].detach()\n        CAM_batch[i] = torch.tensordot(class_weights, feature_map, dims=1)\n        \n    # upsampling feature map to size of image:\n    image_width = img_batch.shape[-1]\n    image_height = img_batch.shape[-2]\n    resized_CAM_batch = T.Resize((image_height, image_width))(CAM_batch)\n    \n    # move minimal value in tensor to zero, to avoid extinction when summing over the tensor:\n    resized_CAM_batch -= torch.min(resized_CAM_batch)\n    normalization_factor = torch.sum(resized_CAM_batch) + 1e-8\n    resized_CAM_batch /= normalization_factor\n\n    return resized_CAM_batch\n\n\ndef random_box(im_width, im_height, alpha, minimal_width=3, minimal_height=3):\n    \"\"\"\n    Returns a random box=(x1, y1, x2, y2) with \n    0 < x1, x2 < im_width\n    and \n    0< y1, y2, < im_height \n    that spans an area such that:\n    lambda_img = (x2 - x1) * (y2 - y1) / (im_width * im_height), \n    where lambda_img is randomly drawn from a beta-distribution beta(alpha, alpha)\n    \"\"\"\n    random_width = im_width + 1\n    random_height = 0\n    \n    while random_width > im_width or \\\n    random_height > im_height or \\\n    random_height < minimal_height or \\\n    random_width < minimal_width:\n        lambda_img = torch.distributions.beta.Beta(torch.tensor([alpha]), torch.tensor([alpha])).sample().item()\n        if lambda_img < 1:\n            low = int(torch.maximum(torch.tensor(lambda_img * im_height), torch.tensor(minimal_height)))\n            high = torch.minimum(torch.tensor(lambda_img * im_width * im_height/ minimal_width), torch.tensor(im_height))\n            high = int(torch.maximum(high, torch.tensor(minimal_height)))\n            if low > high:\n                print(\"false low\", low)\n                print(\"false high\", high)\n                print(\"lambda_img\", lambda_img)\n                raise ValueError\n            elif low + 1 < high - 1:\n                random_height = torch.randint(low + 1, high - 1, (1,1)).item()\n            elif low + 1 >= high -1:\n                random_height = im_height\n                random_width = im_width\n            random_width = int(torch.floor(torch.tensor(lambda_img * im_width * im_height / random_height)))\n\n    left_upper_x = torch.randint(0, im_width - random_width + 1, (1,1)).item()\n    left_upper_y = torch.randint(0, im_height - random_height + 1, (1,1)).item()\n    box = (left_upper_x,\n           left_upper_y,\n           left_upper_x + random_width - 1,\n           left_upper_y + random_height - 1)\n\n    return box\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# test : does TF.crop copy or just create a view?\n# answer: new object at different place in storage\n#import torchvision.transforms.functional as TF\n\n#x11 = 2\n#y11 = 2\n#height_box1 = 5\n#width_box1 = 5\n#features = np.zeros([32,3,224,224])\n#for data in train_dl:\n#        features = data[\"image\"]\n#        break\n#cropped_features1 = TF.crop(features, top=x11, left=y11, height=height_box1, width=width_box1)\n#cropped_features2 = TF.crop(features, top=x11, left=y11, height=height_box1, width=width_box1)\n\n#print(cropped_features2 is cropped_features1) # not the same object\n#print(id(features), id(cropped_features1), id(cropped_features2)) # different places in storage","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# tests for function \"random_box\":\n#import torch\n#import time\n\n#im_width = 224\n#im_height = 224\n#alpha = 2.\n\n#im_width = 224\n#im_height = 224\n#alpha = 1.\n\n#im_width = 224\n#im_height = 224\n#alpha = 5.\n\n#im_width = 224\n#im_height = 512\n#alpha = 2.\n\n#im_width = 1024\n#im_height = 224\n#alpha = 2.\n\n#start_time = time.process_time()\n#for i in range(1000):\n#    b = random_box(im_width, im_height, alpha, minimal_width=3, minimal_height=3)\n#elapsed_time = time.process_time() -start_time\n#print(\"elapsed_time:\", elapsed_time)        ","metadata":{"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Code for training and evaluation","metadata":{}},{"cell_type":"code","source":"import torch.nn as nn\n\n\ndef simple_train_model(model, augmentation_transform, optimizer, inputs, labels):\n    \"\"\"\n    Per batch training - no snapmix augmentation.\n    \"\"\"\n    optimizer.zero_grad()\n    _, y_scores = model(augmentation_transform(inputs))\n    loss = nn.CrossEntropyLoss()(y_scores, labels)\n    loss.backward()\n    optimizer.step()\n\n    # give some feedback how it is going:\n    return loss.item()\n    \n    \ndef snapmix_train_model(model, optimizer, inputs, labels, alpha = 3.):\n    \"\"\"\n    Per batch training - with snapmix augmentation.\n    Uses GPU if possible\n    \"\"\"\n    #use GPU if possible\n    device = (torch.device(\"cuda\") if torch.cuda.is_available() else torch.device(\"cpu\"))\n\n    with torch.no_grad():\n        img_batch, box_weights1, box_weights2 = snapmix_batch_augmentation(model, inputs, labels, alpha=alpha)\n\n    img_batch = img_batch.to(device=device)\n    box_weights1 = box_weights1.to(device=device)\n    box_weights2 = box_weights2.to(device=device)\n    optimizer.zero_grad()\n    _, y_scores = model(img_batch) # y_scores: predicted y's in raw-score/logit-form\n    loss = snapmix_batch_loss(labels, y_scores, box_weights1 = box_weights1, box_weights2 = box_weights2)\n    loss.backward()\n    optimizer.step\n    \n    # give some feedback how it is going:\n    return loss.item()\n        \n\ndef predict(y_scores):\n    if len(y_scores.shape)==1:\n        y_scores = torch.unsqueeze(y_scores,dim=0)\n    probabs = nn.Softmax(dim=1)\n    return torch.argmax(probabs(y_scores), dim=1)\n    \n    ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Training and evaluation loop here","metadata":{}},{"cell_type":"code","source":"# THIS IS THE TRAINING- AND EVALUATION LOOP:\n\nimport torch\nimport torch.nn as nn\nfrom torchvision import transforms as T\nimport time\n\n\n# use GPU if available:\ndevice = (torch.device(\"cuda\") if torch.cuda.is_available() else torch.device(\"cpu\"))\nprint(f\"Training on device {device}.\")\n\n# parameters:\n\n# number of epochs to train - takes approx. 5 mins per epoch:\nepochs = 20\n\n# number of epochs after which to thaw the extractor (applies only for newly build model, not when best model is loaded):\nthaw_after_epochs = 3\n\n# probability if snapmix augmentation should be applied:\nsnapmix_probab = 0.5 # value according to Huang et al. 2020 \n\n# alpha parameter for beta-distribution in snap-mix augmentation:\nsmix_alpha = 5. # value according to Huang et al. 2020 \n\n# Build model or load existing former \"best model\":\nload_best_model = True\n\n# load best model if it exists - unfreeze feature-extractor:\nif load_best_model and os.path.exists(os.path.join(\"./\", \"best_metric_model.pth\")):\n    model = Cassava_resnet50()\n    best_dict = torch.load(os.path.join(\"./\", \"best_metric_model.pth\"))\n    model.load_state_dict(best_dict)\n    for params in model.featureNet.parameters():\n        param.requires_grad = False\n    model.to(device=device)\n    # best model is already pretrained so training of the thawed-layers\n    # can continue:\n    thaw_after_epochs = 0\n    print(\"Loaded best previous model.\")\nelse:\n    model = Cassava_resnet50(freeze_featureNet = True).to(device=device) \n    print(\"New model build.\")\n\n# initialize the optimizer - after moving the model to the device:\noptimizer= torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9) # do this after moving to GPU\n\n\n# finally - here comes the training-loop:\naccuracies = []\nbest_acc = -1\nlosses = []\naugmentation = T.Compose([\n        T.RandomVerticalFlip(p=0.5),\n        T.RandomAffine(30, translate=[.2,.2], scale=None, shear=[-10,10,-10,10]),\n])\n\nfor ep in range(epochs):\n    # thaw last conv layer of featureNet after 5 epochs:\n    if ep == thaw_after_epochs:\n        print(\"Thawing feature extractor.\")\n        for param in model.featureNet[7].parameters():\n            param.requires_grad = True\n    \n    start_time = time.process_time()\n    print(\"--- Epoch: {} ---\".format(ep))\n    loss_train = 0.0\n    loss = 0.0\n    samples_count = 0\n    model.train()\n    for i, batch in enumerate(train_dl):\n        inputs = batch[\"image\"].to(device=device)\n        labels = batch[\"label\"].to(device=device)\n        samples_count += len(labels)\n        s = np.random.uniform(0,1)\n        if s <= snapmix_probab:\n            loss = snapmix_train_model(model, optimizer, inputs, labels, alpha = smix_alpha)\n        else:\n            loss = simple_train_model(model, augmentation, optimizer, inputs, labels)\n        loss_train += loss\n        # give some feedback to the user:\n        if i > 0 and i%100 == 0:\n            print(f\"- Batch {i} -\")\n            print(f\"- avg. loss: {loss_train / samples_count}\")\n    \n    model.eval()\n    print(f\"-- Evaluation Epoch {ep} Started --\")\n    for i, batch in enumerate(eval_dl):\n        inputs = batch[\"image\"].to(device=device)\n        labels = batch[\"label\"].to(device=device)\n        loss = 0.\n        _, y_scores = model(inputs)\n        preds = predict(y_scores)\n        accuracy = torch.sum(preds == labels)/ len(labels)\n        accuracies.append(accuracy.item())\n        loss = nn.CrossEntropyLoss()(y_scores, labels)\n        losses.append(loss.item())\n    \n    mean_acc = np.mean(accuracies)\n    mean_loss = np.mean(losses)\n    elapsed_time = time.process_time() - start_time \n    print(\"Epoch mean accuracy: {0} --- mean loss: {1}\".format(mean_acc, mean_loss))\n    print(f\"Epoch elapsed time: {elapsed_time} \")\n    # save the best model:\n    if best_acc < mean_acc:\n        best_acc = mean_acc\n        best_at_epoch = ep\n        torch.save(model.state_dict(), os.path.join(\"./\", \"best_metric_model.pth\"))\n        print(\"model saved as best metric model\")\n\nprint(\"Training completed.\")\nprint(f\"best accuracy: {best_acc}\")\nprint(f\"best model saved at epoch: {best_at_epoch}\")\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Code for Kaggle Submission","metadata":{}},{"cell_type":"code","source":"# code for kaggle submission - does NOT use CassavaTestDataset (...seemed to work without in the test...):\n\nimport pandas as pd\nfrom pathlib import Path\nimport os\nfrom torch.utils.data import DataLoader\n\n# load best performing model:\noutput_base_path = Path(\"./\") # best model resides in my output-path\nbest_model = Cassava_resnet50()\ndict_best_model = torch.load(os.path.join(output_base_path, \"best_metric_model.pth\"))\nbest_model.load_state_dict(dict_best_model)\nbest_model.to(device=device)\nmodel.eval()\n\n#load test data:\ntest_base_path = Path('../input/cassava-leaf-disease-classification') # the assumed Kaggle autograder path to image directory and csv file\ntest_directory = os.path.join(test_base_path,'test_images') # the assumed Kaggle autograder test images directory\ntest_df = pd.read_csv(os.path.join(test_base_path, \"sample_submission.csv\")) # the assumed Kaggle-autograder csv file\n\n# define dataloader for submission: \ntest_ds = CassavaImageDataset(test_df, test_directory, transform=preprocess_resnet50)\ntest_dl = DataLoader(test_ds, batch_size=32, shuffle=False)\n\nall_img_names = []\nall_label_predictions = []\nfor i, batch in enumerate(test_dl):\n    inputs = batch[\"image\"].to(device=device)\n    _, y_scores = model(inputs)\n    predictions = predict(y_scores)\n    all_label_predictions.extend(predictions.to(device=\"cpu\").numpy()) # convert to numpy to get the numbers in the batch-tensor\n    all_img_names.extend(batch[\"img_name\"])\n    \nsubmission_df=pd.DataFrame({\"image_id\":all_img_names, \"label\":all_label_predictions})\nsubmission_df.to_csv(\"submission.csv\",index=False) # use index=False to prevend pandas from adding an additional index-column","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## CassavaTestDataset - not used","metadata":{}},{"cell_type":"code","source":"# Definition of CassavaTestDataset\n# might be necessary since the CassavaImageDataset tries to return the label of an image - which is not possible for test-data\n\n#import os\n#import pandas as pd\n#from PIL import Image\n#from torch.utils.data import Dataset\n\n#class CassavaTestDataset(Dataset):\n#    \"\"\"\n#    Reads image name and label from pandas data-frame, loads data via PIL, applies transform to image and label and return (image, label) pair.\n#    \n#    Args:\n#        label_image_dataframe (pandas datafram): pandas data-frame containing image name and label\n#        img_dir_path (string): path to the directory containing the images\n#        transform: transform to be applied to the data/images\n#        label_transform: transform to be applied to the labels\n    \n#    Returns:\n#        sample (dictionary): {\"image\": image, \"label\": label, \"img_name\" : image_name}\n#    \"\"\"\n#    def __init__(self, image_label_dataframe, img_dir_path, transform=None, label_transform=None):\n#        self.image_label_df = image_label_dataframe\n#        self.img_dir_path = img_dir_path\n#        self.transform = transform\n#        self.label_transform = label_transform\n\n#    def __len__(self):\n#        return len(self.image_label_df)\n\n#    def __getitem__(self, idx):\n#        image_name = self.image_label_df.iloc[idx, 0]\n#        img_path = os.path.join(self.img_dir_path, image_name)\n#        image = Image.open(img_path)\n#        if self.transform is not None:\n#            image = self.transform(image)\n#        if self.label_transform is not None:\n#            label = self.label_transform(label)\n#        sample = {\"image\": image, \"img_name\" : image_name}\n        \n#        return sample","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# test for kaggle submission code:\n# read the submission.csv:\n#sub_df = pd.read_csv(os.path.join(output_base_path, \"submission.csv\"))\n# look at it's content:\n#sub_df.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":" Test Snapmix Function","metadata":{}}]}