{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\nimport shutil\n\nfrom glob   import glob\nfrom PIL    import Image\n\nimport pickle\n\nimport numpy  as np\nimport pandas as pd\n\nfrom torch                  import cat, no_grad\nfrom torch.utils.data       import DataLoader\nfrom torchvision.transforms import ToTensor, Grayscale, Resize, Compose","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"# Image dimensions\nHEIGHT = 137\nWIDTH  = 236\n\n# Bounding Box\ndef bbox(image):\n    \"\"\"\n    Determines the bounding boxes for images to remove empty space where possible\n    :param image:\n    :return:\n    \"\"\"\n    for i in range(image.shape[1]):\n        if not np.all(image[:,i] == 0):\n            x_min = i - 13 if (i > 13) else 0\n            break\n\n    for i in reversed(range(image.shape[1])):\n        if not np.all(image[:,i] == 0):\n            x_max = i + 13 if (i < WIDTH - 13) else WIDTH\n            break\n\n    for i in range(image.shape[0]):\n        if not np.all(image[i] == 0):\n            y_min = i - 10 if (i > 10) else 0\n            break\n\n    for i in reversed(range(image.shape[0])):\n        if not np.all(image[i] == 0):\n            y_max = i + 10 if (i < HEIGHT - 10) else HEIGHT\n            break\n\n    return x_min, y_min, x_max, y_max\n\n# Create a folder\nos.makedirs(\"images\")\n\n# Convert the files to images\nfor fl in glob(\"../input/bengaliai-cv19/test_image_data_*.parquet\"):\n    # Load the dataset\n    df = pd.read_parquet(fl)\n\n    # Fetch the images and IDs\n    ids, images = df.iloc[:, 0], df.iloc[:, 1:].values.reshape(-1, HEIGHT, WIDTH)\n    del(df)\n\n    # Save each image\n    for id,image in zip(ids, images):\n        # Input data is inverted\n        image_ = 255 - image\n\n        # Remove the boundaries of the image\n        image_ = image_[5:-5, 5:-5]\n\n        # Apply max normalization\n        image_ = (image_ * (255.0 / image_.max())).astype(np.uint8)\n\n        # Filter low-intensity pixels\n        image_[image_ < 50]  = 0\n        #image_[image_ >= 100] = 255\n\n        # Crop the image\n        x_min, y_min, x_max, y_max = bbox(image_)\n        image_ = image_[y_min:y_max, x_min:x_max]\n\n        image_ = Image.fromarray(image_)\n\n        # Save\n        image_.save(\"images/{}.png\".format(id))\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class Dataset(object):\n    def __init__(self, image_folder:str):\n        \"\"\"\n        Class for creating a dataset for model inference\n        \"\"\"\n        # Class attributes\n        self.image_list = glob(image_folder + \"/*.png\")\n        self.transform  = Compose([ Grayscale(num_output_channels=1)\n                                  , Resize(size=(128, 128))\n                                  , ToTensor()\n                                  ]\n                                 )\n\n    def __len__(self):\n        return len(self.image_list)\n\n    def __getitem__(self, idx):\n        # Load the image\n        img = Image.open(self.image_list[idx])\n\n        # Convert to a tensor\n        img = self.transform(img)\n\n        return img\n\n# Create a dataset\nds = Dataset(image_folder=\"images\")\n\n# Create a loader\nloader = DataLoader(ds, batch_size=512, shuffle=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from collections import OrderedDict\nimport math\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\n#%% Model class\nclass SeResNeXt101(nn.Module):\n    def __init__( self\n                , n_class    =1001\n                , input_shape=[1, 128, 128]\n                ):\n        super(SeResNeXt101, self).__init__()\n\n        self.model = SENet( in_channels           =input_shape[0]\n                          , block                 =SEResNeXtBottleneck\n                          , layers                =[3, 4, 23, 3]\n                          , groups                =32\n                          , reduction             =16\n                          , dropout_p             =None\n                          , inplanes              =64\n                          , input_3x3             =False\n                          , downsample_kernel_size=1\n                          , downsample_padding    =0\n                          , num_classes           =n_class\n                          )\n\n    def forward(self, x):\n        x = self.model(x)\n\n        return x\n\n\nclass SEModule(nn.Module):\n    def __init__(self, channels, reduction):\n        super(SEModule, self).__init__()\n        self.avg_pool = nn.AdaptiveAvgPool2d(1)\n        self.fc1 = nn.Conv2d(channels, channels // reduction, kernel_size=1,\n                             padding=0)\n        self.relu = nn.LeakyReLU(negative_slope=0.1)\n        self.fc2 = nn.Conv2d(channels // reduction, channels, kernel_size=1,\n                             padding=0)\n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, x):\n        module_input = x\n        x = self.avg_pool(x)\n        x = self.fc1(x)\n        x = self.relu(x)\n        x = self.fc2(x)\n        x = self.sigmoid(x)\n        return module_input * x\n\n\nclass Bottleneck(nn.Module):\n    \"\"\"\n    Base class for bottlenecks that implements `forward()` method.\n    \"\"\"\n    def forward(self, x):\n        residual = x\n\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        out = self.relu(out)\n\n        out = self.conv3(out)\n        out = self.bn3(out)\n\n        if self.downsample is not None:\n            residual = self.downsample(x)\n\n        out = self.se_module(out) + residual\n        out = self.relu(out)\n\n        return out\n\n\nclass SEBottleneck(Bottleneck):\n    \"\"\"\n    Bottleneck for SENet154.\n    \"\"\"\n    expansion = 4\n\n    def __init__(self, inplanes, planes, groups, reduction, stride=1,\n                 downsample=None):\n        super(SEBottleneck, self).__init__()\n        self.conv1 = nn.Conv2d(inplanes, planes * 2, kernel_size=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(planes * 2)\n        self.conv2 = nn.Conv2d(planes * 2, planes * 4, kernel_size=3,\n                               stride=stride, padding=1, groups=groups,\n                               bias=False)\n        self.bn2 = nn.BatchNorm2d(planes * 4)\n        self.conv3 = nn.Conv2d(planes * 4, planes * 4, kernel_size=1,\n                               bias=False)\n        self.bn3 = nn.BatchNorm2d(planes * 4)\n        self.relu = nn.LeakyReLU(negative_slope=0.1)\n        self.se_module = SEModule(planes * 4, reduction=reduction)\n        self.downsample = downsample\n        self.stride = stride\n\n\nclass SEResNetBottleneck(Bottleneck):\n    \"\"\"\n    ResNet bottleneck with a Squeeze-and-Excitation module. It follows Caffe\n    implementation and uses `stride=stride` in `conv1` and not in `conv2`\n    (the latter is used in the torchvision implementation of ResNet).\n    \"\"\"\n    expansion = 4\n\n    def __init__(self, inplanes, planes, groups, reduction, stride=1,\n                 downsample=None):\n        super(SEResNetBottleneck, self).__init__()\n        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False,\n                               stride=stride)\n        self.bn1 = nn.BatchNorm2d(planes)\n        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, padding=1,\n                               groups=groups, bias=False)\n        self.bn2 = nn.BatchNorm2d(planes)\n        self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)\n        self.bn3 = nn.BatchNorm2d(planes * 4)\n        self.relu = nn.LeakyReLU(negative_slope=0.1)\n        self.se_module = SEModule(planes * 4, reduction=reduction)\n        self.downsample = downsample\n        self.stride = stride\n\n\nclass SEResNeXtBottleneck(Bottleneck):\n    \"\"\"\n    ResNeXt bottleneck type C with a Squeeze-and-Excitation module.\n    \"\"\"\n    expansion = 4\n\n    def __init__(self, inplanes, planes, groups, reduction, stride=1,\n                 downsample=None, base_width=4):\n        super(SEResNeXtBottleneck, self).__init__()\n        width = math.floor(planes * (base_width / 64)) * groups\n        self.conv1 = nn.Conv2d(inplanes, width, kernel_size=1, bias=False,\n                               stride=1)\n        self.bn1 = nn.BatchNorm2d(width)\n        self.conv2 = nn.Conv2d(width, width, kernel_size=3, stride=stride,\n                               padding=1, groups=groups, bias=False)\n        self.bn2 = nn.BatchNorm2d(width)\n        self.conv3 = nn.Conv2d(width, planes * 4, kernel_size=1, bias=False)\n        self.bn3 = nn.BatchNorm2d(planes * 4)\n        self.relu = nn.LeakyReLU(negative_slope=0.1)\n        self.se_module = SEModule(planes * 4, reduction=reduction)\n        self.downsample = downsample\n        self.stride = stride\n\n\nclass SENet(nn.Module):\n    def __init__(self, in_channels, block, layers, groups, reduction, dropout_p=0.2,\n                 inplanes=128, input_3x3=True, downsample_kernel_size=3,\n                 downsample_padding=1, num_classes=1000):\n        \"\"\"\n        Parameters\n        ----------\n        block (nn.Module): Bottleneck class.\n            - For SENet154: SEBottleneck\n            - For SE-ResNet models: SEResNetBottleneck\n            - For SE-ResNeXt models:  SEResNeXtBottleneck\n        layers (list of ints): Number of residual blocks for 4 layers of the\n            network (layer1...layer4).\n        groups (int): Number of groups for the 3x3 convolution in each\n            bottleneck block.\n            - For SENet154: 64\n            - For SE-ResNet models: 1\n            - For SE-ResNeXt models:  32\n        reduction (int): Reduction ratio for Squeeze-and-Excitation modules.\n            - For all models: 16\n        dropout_p (float or None): Drop probability for the Dropout layer.\n            If `None` the Dropout layer is not used.\n            - For SENet154: 0.2\n            - For SE-ResNet models: None\n            - For SE-ResNeXt models: None\n        inplanes (int):  Number of input channels for layer1.\n            - For SENet154: 128\n            - For SE-ResNet models: 64\n            - For SE-ResNeXt models: 64\n        input_3x3 (bool): If `True`, use three 3x3 convolutions instead of\n            a single 7x7 convolution in layer0.\n            - For SENet154: True\n            - For SE-ResNet models: False\n            - For SE-ResNeXt models: False\n        downsample_kernel_size (int): Kernel size for downsampling convolutions\n            in layer2, layer3 and layer4.\n            - For SENet154: 3\n            - For SE-ResNet models: 1\n            - For SE-ResNeXt models: 1\n        downsample_padding (int): Padding for downsampling convolutions in\n            layer2, layer3 and layer4.\n            - For SENet154: 1\n            - For SE-ResNet models: 0\n            - For SE-ResNeXt models: 0\n        num_classes (int): Number of outputs in `last_linear` layer.\n            - For all models: 1000\n        \"\"\"\n        super(SENet, self).__init__()\n        self.inplanes = inplanes\n        if input_3x3:\n            layer0_modules = [\n                ('conv1', nn.Conv2d(in_channels, 64, 3, stride=2, padding=1,\n                                    bias=False)),\n                ('bn1', nn.BatchNorm2d(64)),\n                ('relu1', nn.LeakyReLU(negative_slope=0.1)),\n                ('conv2', nn.Conv2d(64, 64, 3, stride=1, padding=1,\n                                    bias=False)),\n                ('bn2', nn.BatchNorm2d(64)),\n                ('relu2', nn.LeakyReLU(negative_slope=0.1)),\n                ('conv3', nn.Conv2d(64, inplanes, 3, stride=1, padding=1,\n                                    bias=False)),\n                ('bn3', nn.BatchNorm2d(inplanes)),\n                ('relu3', nn.LeakyReLU(negative_slope=0.1)),\n            ]\n        else:\n            layer0_modules = [\n                ('conv1', nn.Conv2d(in_channels, inplanes, kernel_size=7, stride=2,\n                                    padding=3, bias=False)),\n                ('bn1', nn.BatchNorm2d(inplanes)),\n                ('relu1', nn.LeakyReLU(negative_slope=0.1)),\n            ]\n        # To preserve compatibility with Caffe weights `ceil_mode=True`\n        # is used instead of `padding=1`.\n        layer0_modules.append(('pool', nn.MaxPool2d(3, stride=2,\n                                                    ceil_mode=True)))\n        self.layer0 = nn.Sequential(OrderedDict(layer0_modules))\n        self.layer1 = self._make_layer(\n            block,\n            planes=64,\n            blocks=layers[0],\n            groups=groups,\n            reduction=reduction,\n            downsample_kernel_size=1,\n            downsample_padding=0\n        )\n        self.layer2 = self._make_layer(\n            block,\n            planes=128,\n            blocks=layers[1],\n            stride=2,\n            groups=groups,\n            reduction=reduction,\n            downsample_kernel_size=downsample_kernel_size,\n            downsample_padding=downsample_padding\n        )\n        self.layer3 = self._make_layer(\n            block,\n            planes=256,\n            blocks=layers[2],\n            stride=2,\n            groups=groups,\n            reduction=reduction,\n            downsample_kernel_size=downsample_kernel_size,\n            downsample_padding=downsample_padding\n        )\n        self.layer4 = self._make_layer(\n            block,\n            planes=512,\n            blocks=layers[3],\n            stride=2,\n            groups=groups,\n            reduction=reduction,\n            downsample_kernel_size=downsample_kernel_size,\n            downsample_padding=downsample_padding\n        )\n        self.avg_pool = nn.AvgPool2d(4, stride=1)\n        self.dropout = nn.Dropout(dropout_p) if dropout_p is not None else None\n        self.last_linear = nn.Linear(512 * block.expansion, num_classes)\n\n    def _make_layer(self, block, planes, blocks, groups, reduction, stride=1,\n                    downsample_kernel_size=1, downsample_padding=0):\n        downsample = None\n        if stride != 1 or self.inplanes != planes * block.expansion:\n            downsample = nn.Sequential(\n                nn.Conv2d(self.inplanes, planes * block.expansion,\n                          kernel_size=downsample_kernel_size, stride=stride,\n                          padding=downsample_padding, bias=False),\n                nn.BatchNorm2d(planes * block.expansion),\n            )\n\n        layers = []\n        layers.append(block(self.inplanes, planes, groups, reduction, stride,\n                            downsample))\n        self.inplanes = planes * block.expansion\n        for i in range(1, blocks):\n            layers.append(block(self.inplanes, planes, groups, reduction))\n\n        return nn.Sequential(*layers)\n\n    def features(self, x):\n        x = self.layer0(x)\n        x = self.layer1(x)\n        x = self.layer2(x)\n        x = self.layer3(x)\n        x = self.layer4(x)\n        return x\n\n    def logits(self, x):\n        x = self.avg_pool(x)\n        if self.dropout is not None:\n            x = self.dropout(x)\n        x = x.view(x.size(0), -1)\n        x = self.last_linear(x)\n        return x\n\n    def forward(self, x):\n        x = self.features(x)\n        x = self.logits(x)\n        return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class MobileNetV2(nn.Module):\n    def __init__( self\n                , n_class    =1000\n                , input_shape=[1,224,224]\n                , width_mult =1.0\n                , name       =\"MobileNetV2 Model\"\n                ):\n        \"\"\"\n        TODO: Update documentation\n        :param n_class:\n        :param input_shape:\n        :param width_mult:\n        :param name:\n        \"\"\"\n        super(MobileNetV2, self).__init__()\n\n        # Class attributes\n        self.name = name\n\n        # Network architecture\n        interverted_residual_setting = [\n            # t, c, n, s\n            [1, 16, 1, 1],\n            [6, 24, 2, 2],\n            [6, 32, 3, 2],\n            [6, 64, 4, 2],\n            [6, 96, 3, 1],\n            [6, 160, 3, 2],\n            [6, 320, 1, 1],\n        ]\n\n        # Assertions\n        assert input_shape[1] % 32 == 0\n\n        # Create the first layer\n        self.layers = nn.ModuleList([Conv2D_BN(in_features=input_shape[0], out_features=32, stride=2)])\n\n        # Build the residual blocks\n        in_channels = self.layers[0].conv.out_channels\n        for t, c, n, s in interverted_residual_setting:\n            out_channels = make_divisible(c * width_mult) if t > 1 else c\n            for i in range(n):\n                if i == 0:\n                    self.layers.append(InvertedResidual( in_features =in_channels\n                                                       , out_features=out_channels\n                                                       , stride      =s\n                                                       , expand_ratio=t\n                                                       )\n                                     )\n                else:\n                    self.layers.append(InvertedResidual( in_features =in_channels\n                                                       , out_features=out_channels\n                                                       , stride      =1\n                                                       , expand_ratio=t\n                                                       )\n                                      )\n\n                in_channels = out_channels\n\n        # Build the remaining convolutional layer\n        self.layers.append(Conv2D_BN( in_features =in_channels\n                                    , out_features=make_divisible(1280 * width_mult) if width_mult > 1.0 else 1280\n                                    , kernel_size =1\n                                    , stride      =1\n                                    , padding     =0\n                                    )\n                          )\n\n        # Add the classifier\n        self.layers.append(nn.Linear( in_features =self.layers[-1].conv.out_channels\n                                    , out_features=n_class\n                                    )\n                          )\n\n        # Initialize weights\n        self._init_weight()\n\n    # Forward function\n    def forward(self, x):\n        # Pass through the first layer\n        for layer in self.layers[:-1]:\n            x = layer(x)\n\n        # Average pooling\n        x = x.mean(3).mean(2)\n\n        # Classification\n        x = self.layers[-1](x)\n\n        return x\n\n    # Weight initialization\n    def _init_weight(self):\n        for m in self.modules():\n            #print(m)\n            if isinstance(m, nn.Conv2d):\n                nn.init.kaiming_normal_(m.weight)\n            elif isinstance(m, nn.BatchNorm2d):\n                m.weight.data.fill_(1.0)\n                m.bias.data.zero_()\n            elif isinstance(m, nn.Linear):\n                m.weight.data.normal_(0, 0.01)\n                m.bias.data.zero_()\n\n\n#%% Utilities\nclass Conv2D_BN(nn.Module):\n    def __init__(self, in_features, out_features, kernel_size=3, stride=1, padding=1, groups=1, activation=True):\n        \"\"\"\n        Convolution followed by Batch Normalization, with optional Mish activation\n        :param in_features:\n        :param out_features:\n        :param kernel_size:\n        :param stride:\n        :param padding:\n        :param groups:\n        :param activation:\n        \"\"\"\n        super(Conv2D_BN, self).__init__()\n\n        # Class parameters\n        self.activation = activation\n\n        # Convolution and BatchNorm class\n        self.conv = nn.Conv2d( in_channels =in_features\n                             , out_channels=out_features\n                             , kernel_size =kernel_size\n                             , stride      =stride\n                             , padding     =padding\n                             , groups      =groups\n                             , bias        =False\n                             )\n\n        self.bn = nn.BatchNorm2d(num_features=out_features, momentum=0.9)\n\n    def forward(self, x):\n        x = self.conv(x)\n        x = self.bn(x)\n\n        if self.activation:\n            x = F.leaky_relu(x, negative_slope=0.1)\n\n        return x\n\n\ndef make_divisible(x, divisible_by=8):\n    return int(np.ceil(x * 1. / divisible_by) * divisible_by)\n\n\nclass InvertedResidual(nn.Module):\n    def __init__( self\n                , in_features\n                , out_features\n                , stride\n                , expand_ratio\n                ):\n        \"\"\"\n        MobileNet Inverted Residual block\n        :param in_features:\n        :param out_features:\n        :param stride:\n        :param expand_ratio:\n        \"\"\"\n        super(InvertedResidual, self).__init__()\n\n        # Input validation\n        assert stride in [1, 2]\n\n        # Class parameter\n        self.stride = stride\n\n        # Calculate the hidden dimension\n        hidden_dim = int(in_features * expand_ratio)\n\n        # Determine whether to use a residual connection\n        self.res_connect = self.stride == 1 and in_features == out_features\n\n        if expand_ratio == 1:\n            conv_1 = Conv2D_BN( in_features =hidden_dim\n                              , out_features=hidden_dim\n                              , kernel_size =3\n                              , stride      =stride\n                              , padding     =1\n                              , groups      =hidden_dim\n                              )\n\n            conv_2 = Conv2D_BN( in_features =hidden_dim\n                              , out_features=out_features\n                              , kernel_size =1\n                              , stride      =1\n                              , padding     =0\n                              , activation  =False\n                              )\n\n            self.layers = nn.ModuleList([conv_1, conv_2])\n\n        else:\n            conv_1 = Conv2D_BN(in_features  =in_features\n                              , out_features=hidden_dim\n                              , kernel_size =1\n                              , stride      =1\n                              , padding     =0\n                              )\n\n            conv_2 = Conv2D_BN( in_features =hidden_dim\n                              , out_features=hidden_dim\n                              , kernel_size =3\n                              , stride      =stride\n                              , padding     =1\n                              , groups      =hidden_dim\n                              )\n\n            conv_3 = Conv2D_BN( in_features =hidden_dim\n                              , out_features=out_features\n                              , kernel_size =1\n                              , stride      =1\n                              , padding     =0\n                              , activation  =False\n                              )\n\n            self.layers = nn.ModuleList([conv_1, conv_2, conv_3])\n\n    def forward(self, x):\n        if self.res_connect:\n            return x + self._block_forward(x, self.layers)\n        else:\n            return self._block_forward(x, self.layers)\n\n    @staticmethod\n    def _block_forward(x, block):\n        for layer in block:\n            x = layer(x)\n\n        return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Load the models\nmodel_root = \"../input/handwritten-grapheme-models/\"\nmodels = {}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"for name,checkpoint,model,classes in zip( [\"root\", \"vowel\", \"consonant\"]\n                                        , [ model_root + \"root.pth\"\n                                          , model_root + \"vowel.pth\"\n                                          , model_root + \"consonant.pth\"\n                                          ]\n                                        , [SeResNeXt101, MobileNetV2, MobileNetV2]\n                                        , [168, 11, 7]\n                                        ):\n    # Instantiate a model\n    net = model(n_class=classes, input_shape=[1, 128, 128])\n\n    # Load the state dict\n    net.load_state_dict(torch.load(checkpoint))\n    \n    models[name] = net","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Predict\npredictions = {}\nfor feature,model in models.items():\n    model.cuda()\n    model.eval()\n    with no_grad():\n        predictions[feature] = cat([model(x.cuda()).cpu() for x in loader]).softmax(dim=-1).argmax(dim=-1).numpy()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Output\nids    = []\nlabels = []\norders = []\nfor i,image in enumerate(ds.image_list):\n    j = int(image.split(\".\")[0].split(\"_\")[-1])\n    \n    ids.append(\"Test_{}_grapheme_root\".format(j))\n    ids.append(\"Test_{}_vowel_diacritic\".format(j))\n    ids.append(\"Test_{}_consonant_diacritic\".format(j))\n\n    labels.append(predictions[\"root\"][i])\n    labels.append(predictions[\"vowel\"][i])\n    labels.append(predictions[\"consonant\"][i])\n    \n    orders.append(j)\n    orders.append(j + 0.3)\n    orders.append(j + 0.6)\n\n# Create a submission DataFrame\nsubmission_df = pd.DataFrame({\"row_id\":ids, \"target\":labels, \"order\":orders})\n\n# Reorder to be safe\nsubmission_df.sort_values(\"order\", inplace=True)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Write output\nsubmission_df[[\"row_id\", \"target\"]].to_csv('submission.csv', index=False)\n\n# Clean up\nshutil.rmtree(\"images\")","execution_count":null,"outputs":[]}],"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":4,"nbformat_minor":1}