{"cells":[{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install catalyst --upgrade","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"from pathlib import Path\nimport pandas as pd\nimport numpy as np\nfrom random import choices, sample\nfrom collections import Counter\n\nfrom plotly import graph_objects as go\n\nfrom sklearn.model_selection import train_test_split\n\nimport torch\nfrom torch import nn\nfrom torch.nn import functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.utils.data.sampler import Sampler\nimport albumentations as A\n\nfrom catalyst import dl\nfrom catalyst import utils\nfrom catalyst import data\nfrom catalyst.contrib.nn.criterion import TripletMarginLossWithSampler\n\nimport cv2","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"utils.set_global_seed(42)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def find_value_ids(it, value):\n    \"\"\"\n    Args:\n        it: list of any\n        value: query element\n\n    Returns:\n        indices of the all elements equal x0\n    \"\"\"\n    if isinstance(it, np.ndarray):\n        inds = list(np.where(it == value)[0])\n    else:  # could be very slow\n        inds = [i for i, el in enumerate(it) if el == value]\n    return inds\n\n\nclass BalanceBatchSampler(Sampler):\n    \"\"\"\n    This kind of sampler can be used for both metric learning and\n    classification task.\n    Sampler with the given strategy for the C unique classes dataset:\n    - Selection P of C classes for the 1st batch\n    - Selection K instances for each class for the 1st batch\n    - Selection P of C - P remaining classes for 2nd batch\n    - Selection K instances for each class for the 2nd batch\n    - ...\n    The epoch ends when there are no classes left.\n    So, the batch sise is P * K except the last one.\n    Thus, in each epoch, all the classes will be selected once, but this\n    does not mean that all the instances will be selected during the epoch.\n    One of the purposes of this sampler is to be used for\n    forming triplets and pos/neg pairs inside the batch.\n    To guarante existance of these pairs in the batch,\n    P and K should be > 1. (1)\n    Behavior in corner cases:\n    - If a class does not contain K instances,\n    a choice will be made with repetition.\n    - If C % P == 1 then one of the classes should be dropped\n    otherwise statement (1) will not be met.\n    This type of sampling can be found in the classical paper of Person Re-Id,\n    where P equals 32 and K equals 4:\n    `In Defense of the Triplet Loss for Person Re-Identification`_.\n    .. _In Defense of the Triplet Loss for Person Re-Identification:\n        https://arxiv.org/abs/1703.07737\n    \"\"\"\n\n    def __init__(self, labels, p: int, k: int):\n        \"\"\"\n        Args:\n            labels: list of classes labeles for each elem in the dataset\n            p: number of classes in a batch, should be > 1\n            k: number of instances of each class in a batch, should be > 1\n        \"\"\"\n        super().__init__(self)\n        classes = set(labels)\n\n        assert isinstance(p, int) and isinstance(k, int)\n        assert (1 < p <= len(classes)) and (1 < k)\n        assert all(\n            n > 1 for n in Counter(labels).values()\n        ), \"Each class shoud contain at least 2 instances to fit (1)\"\n\n        self._labels = labels\n        self._p = p\n        self._k = k\n\n        self._batch_size = self._p * self._k\n        self._classes = classes\n\n        # to satisfy statement (1)\n        num_classes = len(self._classes)\n        if num_classes % self._p == 1:\n            self._num_epoch_classes = num_classes - 1\n        else:\n            self._num_epoch_classes = num_classes\n\n    @property\n    def batch_size(self) -> int:\n        \"\"\"\n        Returns:\n            this value should be used in DataLoader as batch size\n        \"\"\"\n        return self._batch_size\n\n    @property\n    def batches_in_epoch(self) -> int:\n        \"\"\"\n        Returns:\n            number of batches in an epoch\n        \"\"\"\n        return int(np.ceil(self._num_epoch_classes / self._p))\n\n    def __len__(self) -> int:\n        \"\"\"\n        Returns:\n            number of samples in an epoch\n        \"\"\"\n        return self._num_epoch_classes * self._k\n\n    def __iter__(self):\n        \"\"\"\n        Returns:\n            indeces for sampling dataset elems during an epoch\n        \"\"\"\n        inds = []\n\n        for cls_id in sample(self._classes, self._num_epoch_classes):\n            all_cls_inds = find_value_ids(self._labels, cls_id)\n\n            # we've checked in __init__ that this value must be > 1\n            num_samples_exists = len(all_cls_inds)\n\n            if num_samples_exists < self._k:\n                selected_inds = sample(\n                    all_cls_inds, k=num_samples_exists\n                ) + choices(all_cls_inds, k=self._k - num_samples_exists)\n            else:\n                selected_inds = sample(all_cls_inds, k=self._k)\n\n            inds.extend(selected_inds)\n\n        return iter(inds)\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"train_df = pd.read_csv(\"../input/landmark-recognition-2020/train.csv\")\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_img(id_: str, train=True):\n    if train:\n        root = Path(\"../input/landmark-recognition-2020/train\")\n    else:\n        root = Path(\"../input/landmark-recognition-2020/test\")\n    first_folder = root / str(id_[0])\n    second_folder = first_folder / str(id_[1])\n    third_folder = second_folder / str(id_[2])\n    path_to_img = third_folder / str(id_)\n    img = cv2.imread(str(path_to_img)+\".jpg\")\n    return img","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class ImgDataset(Dataset):\n    def __init__(self, df, transforms = None, train: bool = True):\n        self.id = df.id.values\n        if train:\n            self.labels = df.landmark_id.values\n        self.train = train\n        if transforms is None:\n            transforms = A.Compose([\n                A.Resize(width=224, height=224), \n                A.pytorch.ToTensor()\n            ])\n        self.transforms = transforms\n        \n    def __getitem__(self, idx: int):\n        img = load_img(self.id[idx], train=self.train)\n        tensor_img = self.transforms(image=img)[\"image\"]\n        \n        output = {\"features\": tensor_img}\n        if self.train:\n            label = self.labels[idx]\n            output[\"targets\"] = label\n        return output\n    \n    def __len__(self):\n        return len(self.labels)\n    \n    def get_labels(self):\n        return np.array(self.labels)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df_, valid_df_ = train_test_split(train_df, random_state=42, stratify=train_df.landmark_id.values)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_ds = ImgDataset(train_df_)\nvalid_ds = ImgDataset(valid_df_)\nsampler = BalanceBatchSampler(labels=train_ds.get_labels(), p=10, k=20)\ntrain_dl = DataLoader(train_ds, sampler=sampler, batch_size=sampler.batch_size)\nvalid_dl = DataLoader(valid_ds, sampler=sampler, batch_size=sampler.batch_size)\nloaders = {\"train\": train_dl, \"valid\": valid_dl}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from torchvision import models","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = models.resnext50_32x4d(pretrained=True)\nfor param in model.parameters():\n    param.requires_grad = False\n    \nhead = nn.Sequential(\n    nn.Linear(1000, 512),\n    nn.ReLU(),\n    nn.Linear(512, 100),\n)\nmodel = nn.Sequential(\n    model,\n    head,\n)\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import wandb\n\n#wandb.login(\"never\", \"\")\nwandb.init(project=\"landmarks\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sampler_inbatch = data.HardTripletsSampler(norm_required=False)\ncriterion = TripletMarginLossWithSampler(margin=0.5, sampler_inbatch=sampler_inbatch)\n\n# 4. training with catalyst Runner\ncallbacks = [\n    dl.ControlFlowCallback(dl.CriterionCallback(), loaders=\"train\"),\n    dl.WandbLogger(log_on_batch_end=True, project=\"landmarks\"),\n]\n\nrunner = dl.SupervisedRunner(device=utils.get_device())\n#runner.train(\n#    model=model,\n#    criterion=criterion,\n#    optimizer=optimizer,\n#    callbacks=callbacks,\n#    loaders=loaders,\n#    minimize_metric=False,\n#    verbose=True,\n#    num_epochs=200,\n#)   ","execution_count":null,"outputs":[]},{"metadata":{"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}