{"cells":[{"metadata":{"trusted":true},"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)","execution_count":null,"outputs":[]},{"metadata":{"id":"cWGO2uFHpubx","pycharm":{"name":"#%% md\n"}},"cell_type":"markdown","source":"# Classification task\n\nHi! It's a classification task baseline notebook.\nIt include a data reader, baseline model and submission generator."},{"metadata":{"id":"eHtKdA2dqGEk","trusted":true},"cell_type":"code","source":"from pathlib import Path\nfrom datetime import datetime\nimport numpy as np\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils import data\n\nimport catalyst\nfrom catalyst import dl\nfrom catalyst.utils import metrics, set_global_seed","execution_count":null,"outputs":[]},{"metadata":{"id":"v949FHgsQy62","trusted":true},"cell_type":"code","source":"set_global_seed(42)","execution_count":null,"outputs":[]},{"metadata":{"id":"BgU_DOniQR1p","pycharm":{"name":"#%% md\n"}},"cell_type":"markdown","source":"## Dataset\n\nThis code will help you to generate dataset. If your data have the following folder structure:\n\n```\ndataset/\n    class_1/\n        *.ext\n        ...\n    class_2/\n        *.ext\n        ...\n    ...\n    class_N/\n        *.ext\n        ...\n```\nFirst of all `create_dataset` function goes through a given directory and creates a dictionary `Dict[class_name, List[image]]`.\nThen `create_dataframe` function creates typical `pandas.DataFrame` for further analysis.\nAfter that, `prepare_dataset_labeling` creates a numerical label for each unique class name.\nFinally, to add a column with a numerical label value to the DataFrame, we can use `map_dataframe` function.\n\nAdditionaly let's save the `class_names` for further usage."},{"metadata":{"pycharm":{"name":"#%%\n"},"id":"rFIDQmEORPh1","trusted":true},"cell_type":"code","source":"from catalyst.utils import (\n    create_dataset, create_dataframe, get_dataset_labeling, map_dataframe\n)\n\ndataset = create_dataset(dirs=f\"../input/Imagenette-comp/train/*\", extension=\"*.jpg\")\ndf = create_dataframe(dataset, columns=[\"class\", \"filepath\"])\n\ntag_to_label = get_dataset_labeling(df, \"class\")\nclass_names = [\n    name for name, id_ in sorted(tag_to_label.items(), key=lambda x: x[1])\n]\n\ndf_with_labels = map_dataframe(\n    df, \n    tag_column=\"class\", \n    class_column=\"label\", \n    tag2class=tag_to_label, \n    verbose=False\n)\ndf_with_labels.head()","execution_count":null,"outputs":[]},{"metadata":{"pycharm":{"name":"#%% md\n"},"id":"PnFtDBQjRPiD"},"cell_type":"markdown","source":"And you should split data in `train / valid / test` parts.\nThere are only `train` and `valid` parts, so you must load test data as shows in a code cell."},{"metadata":{"id":"sEhBS2sqQZ1z","trusted":true},"cell_type":"code","source":"from catalyst.utils import split_dataframe_train_test\n\ntrain_data, valid_data = split_dataframe_train_test(\n    df_with_labels, test_size=0.2, random_state=42\n)\ntrain_data, valid_data = (\n    train_data.to_dict(\"records\"),\n    valid_data.to_dict(\"records\"),\n)","execution_count":null,"outputs":[]},{"metadata":{"id":"-1upnlv4RV2m","trusted":true},"cell_type":"code","source":"from catalyst.data.cv.reader import ImageReader\nfrom catalyst.dl import utils\nfrom catalyst.data import ScalarReader, ReaderCompose\n\nnum_classes = len(tag_to_label)\n\nopen_fn = ReaderCompose(\n    [\n        ImageReader(\n            input_key=\"filepath\", output_key=\"features\", rootpath=\"../input/Imagenette-comp/train\"\n        ),\n        ScalarReader(\n            input_key=\"label\",\n            output_key=\"targets\",\n            default_value=-1,\n            dtype=np.int64,\n        ),\n        ScalarReader(\n            input_key=\"label\",\n            output_key=\"targets_one_hot\",\n            default_value=-1,\n            dtype=np.int64,\n            one_hot_classes=num_classes,\n        ),\n    ]\n)","execution_count":null,"outputs":[]},{"metadata":{"pycharm":{"name":"#%% md\n"},"id":"fiFQL6w7RPib"},"cell_type":"markdown","source":"## Augmentation\n\nUse some augmentations to generate more images for training process."},{"metadata":{"id":"G1f7StMdRV0S","trusted":true},"cell_type":"code","source":"import albumentations as albu\nfrom albumentations.pytorch import ToTensorV2 as ToTensor\n\nIMAGE_SIZE = 224\n\ntrain_transform = albu.Compose([\n    albu.HorizontalFlip(p=0.5),\n    albu.LongestMaxSize(IMAGE_SIZE),\n    albu.PadIfNeeded(IMAGE_SIZE, IMAGE_SIZE, border_mode=0),\n    albu.RandomResizedCrop(IMAGE_SIZE, IMAGE_SIZE, p=0.3),\n    albu.Normalize(),\n    ToTensor(),\n])\n\nvalid_transform = albu.Compose([\n    albu.LongestMaxSize(IMAGE_SIZE),\n    albu.PadIfNeeded(IMAGE_SIZE, IMAGE_SIZE, border_mode=0),\n    albu.Normalize(),\n    ToTensor(),\n])\n","execution_count":null,"outputs":[]},{"metadata":{"id":"wJ1hwc8NRVxB","trusted":true},"cell_type":"code","source":"from catalyst.data import Augmentor\n\ntrain_data_transform = Augmentor(\n    dict_key=\"features\", augment_fn=lambda x: train_transform(image=x)[\"image\"]\n)\n\nvalid_data_transform = Augmentor(\n    dict_key=\"features\", augment_fn=lambda x: valid_transform(image=x)[\"image\"]\n)\n","execution_count":null,"outputs":[]},{"metadata":{"id":"f-4HVfMiRuxM","pycharm":{"name":"#%% md\n"}},"cell_type":"markdown","source":"Don't forget to create test loader."},{"metadata":{"pycharm":{"name":"#%%\n"},"id":"R5SAJcJvRPiz","trusted":true},"cell_type":"code","source":"batch_size = 256\nnum_workers = 4\n\ntrain_loader = utils.get_loader(\n    train_data,\n    open_fn=open_fn,\n    dict_transform=train_data_transform,\n    batch_size=batch_size,\n    num_workers=num_workers,\n    shuffle=True,\n    sampler=None,\n    drop_last=True,\n)\n\nvalid_loader = utils.get_loader(\n    valid_data,\n    open_fn=open_fn,\n    dict_transform=valid_data_transform,\n    batch_size=batch_size,\n    num_workers=num_workers,\n    shuffle=False, \n    sampler=None,\n    drop_last=True,\n)\n\nloaders = {\n    \"train\": train_loader,\n    \"valid\": valid_loader\n}","execution_count":null,"outputs":[]},{"metadata":{"pycharm":{"name":"#%% md\n"},"id":"wOki67UiRPjK"},"cell_type":"markdown","source":"## Model\n\nFor the baseline, we will use a ResNet model, we already have examined in the seminar.\nEnhance the model, use any* instruments or module as you like.\n\n*(Don't forget about the rules!)"},{"metadata":{"trusted":true},"cell_type":"code","source":"from torchvision import transforms, models\n\nclass MyResNet50(torch.nn.Module):\n    def __init__(self):\n        super(MyResNet50, self).__init__()\n        self.net = models.resnet50(pretrained=True)\n        \n        # Disable grad for all conv layers\n        for param in self.net.parameters():\n            param.requires_grad = False                \n        \n        # Create some additional layers for ResNet model\n        fc_inputs = self.net.fc.in_features\n        self.net.fc = torch.nn.Sequential(\n            torch.nn.Linear(fc_inputs, 256),\n            torch.nn.ReLU(),\n            torch.nn.Linear(256, 128),\n            torch.nn.Sigmoid(),\n            torch.nn.Linear(128, 10),\n        )  \n    def forward(self, x):\n        x = self.net(x)\n        return x","execution_count":null,"outputs":[]},{"metadata":{"id":"OpOxdntTrAwP","trusted":true},"cell_type":"code","source":"class ResNetBlock(nn.Module):\n    def __init__(self, in_channels, out_channels, stride, p=0.1):\n        super().__init__()\n\n        self.input = nn.Sequential(\n            nn.Conv2d(\n                in_channels,\n                out_channels,\n                kernel_size=3,\n                stride=stride,\n                padding=1,\n            ),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(),\n            nn.Conv2d(\n                out_channels, out_channels, kernel_size=3, stride=1, padding=1\n            ),\n            nn.BatchNorm2d(out_channels),\n        )\n        self.res = nn.Conv2d(\n            in_channels, out_channels, kernel_size=1, stride=stride\n        )\n        self.output = nn.Sequential(nn.BatchNorm2d(out_channels), nn.ReLU())\n\n    def forward(self, x):\n        input = self.input(x)\n        res = self.res(x)\n        return self.output(res + input)\n\n\nclass BaselineModel(nn.Module):\n    def __init__(self, channels=3, in_features=64, num_classes=10, p=0.1):\n        super().__init__()\n\n        self.input = nn.Sequential(\n            nn.Conv2d(\n                channels, in_features, kernel_size=7, stride=2, padding=3\n            ),\n            nn.BatchNorm2d(in_features),\n            nn.ReLU(),\n            nn.MaxPool2d(kernel_size=3, stride=2),\n        )\n\n        self.layer_0 = self._make_layer(in_features, 1)\n        self.layer_1 = self._make_layer(in_features)\n        in_features *= 2\n        self.layer_2 = self._make_layer(in_features)\n        in_features *= 2\n        self.layer_3 = self._make_layer(in_features)\n\n        self.fc = nn.Sequential(\n            nn.AdaptiveAvgPool2d((1, 1)),\n            nn.Flatten(),\n            nn.Linear(2 * in_features, num_classes),\n        )\n\n    def _make_layer(self, in_features, multiplier=2, p=0.1):\n        return nn.Sequential(\n            ResNetBlock(in_features, in_features * multiplier, stride=2, p=p),\n            ResNetBlock(\n                in_features * multiplier,\n                in_features * multiplier,\n                stride=1,\n                p=p,\n            ),\n        )\n\n    def forward(self, x):\n        x = self.input(x)\n        x = self.layer_0(x)\n        x = self.layer_1(x)\n        x = self.layer_2(x)\n        x = self.layer_3(x)\n        return self.fc(x)","execution_count":null,"outputs":[]},{"metadata":{"id":"B26FN-CLq4cF","pycharm":{"name":"#%%\n"},"trusted":true},"cell_type":"code","source":"from catalyst.dl import SupervisedRunner\n\nclass ClassificationRunner(SupervisedRunner):\n    def predict_batch(self, batch):\n        prediction = {\n            \"filepath\": batch[\"filepath\"],\n            \"log_probs\": self.model(batch[self.input_key].to(self.device))\n        }\n        return prediction","execution_count":null,"outputs":[]},{"metadata":{"id":"HOfvcgRNrNWh","trusted":true},"cell_type":"code","source":"model = MyResNet50()\n\n# model = BaselineModel()\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n\nrunner = ClassificationRunner(input_key=\"features\", input_target_key=\"targets\")\nrunner.train(\n    model=model,\n    optimizer=optimizer,\n    criterion=criterion,\n    loaders=loaders,\n    logdir=Path(\"logs\") / datetime.now().strftime(\"%Y%m%d-%H%M%S\"),\n    num_epochs=10,\n    verbose=True,\n    load_best_on_end=True,\n    callbacks={\n        \"optimizer\": dl.OptimizerCallback(\n            metric_key=\"loss\", accumulation_steps=1, grad_clip_params=None,\n        ),\n        \"criterion\": dl.CriterionCallback(\n            input_key=\"targets\", output_key=\"logits\", prefix=\"loss\",\n        ),\n        \"accuracy\": dl.AccuracyCallback(num_classes=10),\n    },\n)","execution_count":null,"outputs":[]},{"metadata":{"pycharm":{"name":"#%% md\n"},"id":"d_bCKBjHRPjh"},"cell_type":"markdown","source":"This code below will generate a submission.\nIt reads images from `test` folder and gathers prediction from the trained model.\nCheck your submission before uploading it into `Kaggle`."},{"metadata":{"id":"KgNpIL-uS5KB","trusted":true},"cell_type":"code","source":"import pandas as pd\nfrom PIL import Image\nfrom tqdm.notebook import tqdm\n\nsubmission = {\"Id\": [], \"Category\": []}\nmodel.eval()\n\ntest_dataset = create_dataset(dirs=f\"../input/Imagenette-comp/test/\", extension=\"*.jpg\")\ntest_data = list({\"filepath\": filepath} for filepath in test_dataset[\"test\"])\n\ntest_open_fn = ReaderCompose(\n    [\n        ImageReader(\n            input_key=\"filepath\", output_key=\"features\", rootpath=\"\"\n        ),\n        ScalarReader(\n            input_key=\"filepath\",\n            output_key=\"filepath\",\n            default_value=\"\",\n            dtype=str,\n        ),\n    ]\n)\n\ntest_loader = utils.get_loader(\n    test_data,\n    open_fn=test_open_fn,\n    dict_transform=valid_data_transform,\n    batch_size=batch_size,\n    num_workers=num_workers,\n    shuffle=False,\n    sampler=None,\n    drop_last=False,\n)\n\nfor prediction in runner.predict_loader(loader=test_loader):\n    prediction[\"labels\"] = [class_names[c] for c in torch.max(prediction[\"log_probs\"], axis=1)[1]]\n    submission[\"Id\"].extend(f.split(\"/\")[4].split(\".\")[0] for f in prediction[\"filepath\"])\n    submission[\"Category\"].extend(prediction[\"labels\"])","execution_count":null,"outputs":[]},{"metadata":{"id":"k8XOQD15S5Gh","trusted":true},"cell_type":"code","source":"pd.DataFrame(submission).to_csv(\"baseline.csv\", index=False)","execution_count":null,"outputs":[]},{"metadata":{"id":"vgXHVCS_TgrS","trusted":true},"cell_type":"code","source":"","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":4}