{"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":"# Домашнее задание «Сверточные нейронные сети: практическое применение»\nПреподаватель: Мария Шеянова, Даниил Корбут, Наталья Баданина, Александр Миленькин, Анастасия Успенская\n\n    Классификация изображений: Cats vs Dogs\n    Обучить модель классификации изображение на 2 класса. \n    Исходные данные и валидация решения на kaggle в рамках контеста Cats vs Dogs.\n    Шаблон ipython-ноутбука для решения можно скачать по ссылке. \n    Решения необходимо прислать в виде ipython-ноутбука с указанием значения метрики на Leaderboard. \n    Задание засчитывается при значениях метрики Log Loss меньше 0.3.\n\n","metadata":{"id":"bxVtA8rNhG1Y"}},{"cell_type":"code","source":"%%bash\npip install timm\npip install -U git+https://github.com/albumentations-team/albumentations","metadata":{"id":"KQWtBxuyAn2J","outputId":"ad0e18bc-dad3-4bab-8366-412d765afa5b","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd \nimport os\nimport sys\nimport torch\nimport torch.nn as nn\nimport cv2\nimport matplotlib.pyplot as plt\nimport torchvision\nfrom torch.utils.data import Dataset, DataLoader, ConcatDataset\nfrom torchvision import transforms\nimport copy\nfrom tqdm.notebook import tqdm\nimport random\nfrom PIL import Image\nfrom pathlib import Path\nimport multiprocessing\nfrom sklearn.model_selection import KFold\nfrom zipfile import ZipFile\n\nimport timm\n\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom catalyst.contrib.nn.schedulers.onecycle import OneCycleLRWithWarmup\n\n%matplotlib inline","metadata":{"id":"Ij1ULsdrXJHa","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ZipFile('../input/dogs-vs-cats-redux-kernels-edition/train.zip',\"r\").extractall()\nZipFile('../input/dogs-vs-cats-redux-kernels-edition/test.zip', \"r\").extractall()\n\ntrain_dir = Path('./train')\ntest_dir = Path('./test')\n\ntrain_files = np.array(os.listdir(train_dir))\ntest_files = np.array(os.listdir(test_dir))\nlen(train_files), len(test_files)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submit = pd.read_csv('../input/dogs-vs-cats-redux-kernels-edition/sample_submission.csv')\nsubmit['file'] = submit.id.astype(str) + '.jpg'\nsubmit.tail(3)","metadata":{"id":"ZXSLiwXHXcMt","outputId":"7a82733e-183b-4bb0-e58b-0b208d2b2777","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_table = pd.DataFrame(train_files, columns=['file'])\ntrain_table['raw'] = train_table.file.str.split('.')\ntrain_table['id'] = train_table.raw.str[1].astype(np.uint32)\ntrain_table['label'] = (train_table.raw.str[0] == 'dog').astype(np.uint8)\ntrain_table.label.hist()","metadata":{"id":"nv4p49JDmakB","outputId":"7c35ed18-dcc5-4bbf-9215-fcbc2ad4bfc5","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Классы распределены равномерно!","metadata":{"id":"IXWIGHyNxfL7"}},{"cell_type":"code","source":"train_table = train_table.drop(columns=['raw'])\ntrain_table","metadata":{"id":"QVV7wyBftiNT","outputId":"0ef09d0e-c02c-4a5a-f1a7-35e91b98feaf","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"NUM_CORES = multiprocessing.cpu_count()\nSEED = 2021\nN_SPLITS = 5\nbatch_size = 32\nimg_size = 224 #384\nnum_classes = 1\nNUM_CORES","metadata":{"id":"f1SHY_m_gzq5","outputId":"10bec138-4a3a-4b72-8377-cd80d10879f3","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Helper Functions","metadata":{"id":"oCoIhO_hg1UH"}},{"cell_type":"code","source":"def seed_everything(seed):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = True\n\ndef get_img(path):\n    im_bgr = cv2.imread(str(path))\n    im_rgb = im_bgr[:, :, ::-1]\n    return im_rgb\n\ndef get_table(files):\n    table = pd.DataFrame(files, columns=['file'])\n    table['raw'] = table.file.str.split('.')\n    table['id'] = table.raw.str[1].astype(np.uint32)\n    table['label'] = (table.raw.str[0] == 'dog').astype(np.uint8)\n    table = table.drop(columns=['raw'])\n    return table\n\ndef show_batch(loader):\n    images, labels = next(iter(loader))\n    examples = images['image'].shape[0]\n    width = int(examples ** .5)\n    height = examples // width\n    _indexes = [(i, j) for i in range(height) for j in range(width)]\n    f, ax = plt.subplots(height, width, figsize=(22, 22))\n    for (i, j), img, label in zip(_indexes, images['image'], labels):\n        ax[i, j].imshow(img.permute(1, 2, 0).numpy().astype(np.uint8))\n        if label == 0:\n            label = 'cat'\n        else:\n            label = 'dog'\n        ax[i, j].set_title(label)\n    f.tight_layout()\n\ndef inverse_normalize(tensor,\n                      mean=torch.tensor([0.485, 0.456, 0.406]),\n                      std=torch.tensor([0.229, 0.224, 0.225])):\n    for t, m, s in zip(tensor, mean, std):\n        t.mul_(s).add_(m)\n    return tensor\n\nthreshold = 0.5\n\ndef show_batch(loader, predict=False, threshold=0.5):\n    images, labels = next(iter(loader))\n    examples = images['image'].shape[0]\n    width = int(examples ** .5)\n    height = examples // width\n    _indexes = [(i, j) for i in range(height) for j in range(width)]\n    f, ax = plt.subplots(height, width, figsize=(22, 22))\n\n    if predict:\n        preds = model(images['image'].to(device))\n        if num_classes != 1:\n            labels_pred = preds.argmax(dim=1)\n        else:\n            labels_pred = torch.sigmoid(preds) >= threshold\n\n    else:\n        labels_pred = ['unpredicted' for i in range(examples)]\n\n    for (i, j), img, label, pred in zip(_indexes, images['image'], labels, labels_pred):\n        img = inverse_normalize(img).numpy().transpose(1, 2, 0)\n        ax[i, j].imshow(img)\n\n        if label == 0:\n            label = 'cat'\n        if label == 1:\n            label = 'dog'\n\n        if pred == 0:\n            pred = 'cat'\n        if pred == 1:\n            pred = 'dog'\n            \n        ax[i, j].set_title(label + '/' + pred)\n    f.tight_layout()\n    \nseed_everything(SEED)","metadata":{"id":"DXp-UOOygSoi","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Dataloader","metadata":{"id":"hHDS_EvKlTnC"}},{"cell_type":"code","source":"class CatsDataset(Dataset):\n    def __init__(self, annotations, img_dir, transform=None, learning_stage='train'):\n        self.img_table = annotations\n        self.img_dir = img_dir\n        self.transform = transform\n        self.learning_stage = learning_stage\n\n    def __len__(self):\n        return self.img_table.shape[0]\n\n    def __getitem__(self, idx):\n        file_name = self.img_table.file[idx]\n        img_path = os.path.join(self.img_dir, file_name)\n        image = get_img(img_path)\n\n        if self.transform:\n                image = self.transform(image=image)\n\n        if self.learning_stage in ['train', 'val']:\n            label = self.img_table.label[idx]\n            sample = image, label\n        else:   \n            sample = image, file_name\n\n        return sample","metadata":{"id":"zwhrokmplS7F","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_train_transforms():\n    return A.Compose([\n                    A.Resize(img_size, img_size, interpolation=2, p=1.),\n                    A.RandomSizedCrop((img_size-24, img_size-24), img_size, img_size, p=0.5),\n                    A.HorizontalFlip(p=0.5),\n                    A.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.1, rotate_limit=20, p=0.5),\n                    A.RGBShift(r_shift_limit=15, g_shift_limit=15, b_shift_limit=15, p=0.5),\n                    A.RandomBrightnessContrast(p=0.5),\n                    A.ColorJitter(p=0.3),\n                    A.CoarseDropout(max_holes=3, max_height=36, max_width=36, p=0.5),\n                    A.RandomGridShuffle(grid=(2, 2), p=0.2),\n                    A.Blur(blur_limit=(3, 5), p=0.1),\n                    A.Normalize(p=1.0), \n                    ToTensorV2(p=1.0),\n                    ], p=1.)\n  \ndef get_valid_transforms():\n    return A.Compose([\n                    A.Resize(img_size, img_size, interpolation=2, p=1.),\n                    A.Normalize(p=1.0),\n                    ToTensorV2(p=1.0),\n                    ], p=1.)","metadata":{"id":"kIVYnsfyohfa","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"kfs = KFold(N_SPLITS)\ntest_table = submit\nfor train, val in kfs.split(train_files):\n    #train_table = get_table(train_files[train])\n    val_table = get_table(train_files[val])","metadata":{"id":"4zEe-iKK3hMl","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dataset = CatsDataset(train_table, train_dir, transform=get_train_transforms(), learning_stage='train')\nval_dataset = CatsDataset(val_table, train_dir, transform=get_valid_transforms(), learning_stage='val')\ntest_dataset = CatsDataset(test_table, test_dir, transform=get_valid_transforms(), learning_stage='test')\n\ntrain_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\nval_loader = torch.utils.data.DataLoader(val_dataset, batch_size=batch_size)\ntest_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size)\n\nloaders = {'train': train_loader,\n           'val': val_loader,\n           'test': test_loader,\n           }","metadata":{"id":"gwHfFpqviC-T","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_batch(loaders['train'])","metadata":{"id":"tr6UXzr6kgrB","outputId":"ffc5431d-4099-4d36-dbed-0b02a6d765d5","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model","metadata":{"id":"vp-VCM6fQjoN"}},{"cell_type":"code","source":"class CatNet(nn.Module):\n    def __init__(self, num_classes, encoder='tf_efficientnet_b5_ns', dropout=0.01):\n        super().__init__()\n        self.dropout = dropout\n        self.backbone = timm.create_model(encoder, pretrained=True)\n        self.classifier = nn.Sequential(nn.Dropout(self.dropout),\n                                        nn.Linear(self.backbone.num_classes, num_classes))\n    def forward(self, x):\n        x = self.backbone(x)\n        x = self.classifier(x)\n        return x\n\n\ndef get_params(model, lr=3e-3, reduce=0.1):\n    return  [\n        {'params': model.backbone.parameters(), 'lr': lr * reduce},\n        {'params': model.classifier.parameters(), 'lr': lr},\n    ]\n        \nmodel_names = ['resnet34',\n               'tf_efficientnet_b1_ns',\n               'tf_efficientnet_b0_ns',\n               'vit_large_patch16_384',\n               'tf_efficientnet_b6_ns',\n               'tf_efficientnet_l2_ns_475']\n\nmodel_name = model_names[2]\nmodel = CatNet(num_classes, model_name)\nparam = get_params(model, lr=1e-4)","metadata":{"id":"kqiMPEktQkRf","outputId":"f71b7b92-6e0f-4539-da84-91e6c2448b7d","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train_model(model, loss, optimizer, scheduler, num_epochs):\n    for epoch in range(num_epochs):\n        print(f'Epoch {epoch}/{num_epochs}:', flush=True)\n\n        for phase in ['TRAIN', 'VALID']:\n\n            if phase == 'TRAIN':\n                dataloader = loaders['train']\n                model.train()  \n            else:\n                dataloader = loaders['val']\n                model.eval() \n\n            running_loss = 0.\n            running_acc = 0.\n\n            for image_batch, label_batch in tqdm(dataloader):\n                image_batch = image_batch['image'].to(device)\n                label_batch = label_batch.to(device).float().view(-1, 1)\n\n                optimizer.zero_grad()\n\n                with torch.set_grad_enabled(phase == 'TRAIN'):\n                    preds = model(image_batch)\n                    loss_value = loss(preds, label_batch)\n                    \n                    if num_classes != 1:\n                        preds_class = preds.argmax(dim=1)\n                    else:\n                        preds_class = torch.sigmoid(preds) >= threshold\n\n                    if phase == 'TRAIN':\n                        loss_value.backward()\n                        optimizer.step()\n\n                # statistics\n                running_loss += loss_value.item()\n                running_acc += (preds_class == label_batch.data).float().mean()\n\n                sys.stdout.write('\\x1b[1K\\r')\n                sys.stdout.write(f'{phase}: Current loss: {loss_value.item():.4f}')\n\n\n            epoch_loss = running_loss / len(dataloader)\n            epoch_acc = running_acc / len(dataloader)\n            \n            scheduler.step(epoch_loss)\n\n            print(f'  Loss={epoch_loss:.4f} Acc={epoch_acc:.4f}', flush=True)\n\n    return model","metadata":{"id":"3v3iwnx5TFDZ","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nmodel = model.to(device)\nif num_classes != 1:\n    loss = torch.nn.CrossEntropyLoss()\nelse:\n    loss = torch.nn.BCEWithLogitsLoss()\n    \noptimizer = torch.optim.AdamW(param)\n#scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=0.2, patience=2, verbose=True)\nscheduler = torch.optim.lr_scheduler.StepLR(optimizer, 1, gamma=0.5)","metadata":{"id":"LaLvlmQfg--p","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = train_model(model, loss, optimizer, scheduler, num_epochs=1)","metadata":{"id":"2CEaOtVUhtZ5","outputId":"ae823591-54ba-4a73-be35-735d18153f1e","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Predict Test","metadata":{"id":"xfZB1VK-yRcn"}},{"cell_type":"markdown","source":"Loss=0.0105 Acc=0.9960","metadata":{}},{"cell_type":"code","source":"model.eval();","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_batch(loaders['test'], predict=True)","metadata":{"id":"KTqal34UBd0O","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"file_name_list = []\npred_list = []\n\nwith torch.no_grad():\n    for image_batch, file_name in tqdm(loaders['test']):\n        image_batch = image_batch['image'].to(device)\n        preds = model(image_batch)\n        if num_classes != 1:\n            labels_pred = preds.argmax(dim=1)\n            preds_class = preds.argmax(dim=1)\n        else:\n            labels_pred = torch.sigmoid(preds) \n            preds_class = labels_pred # >= threshold\n\n        file_name_list += [name for name in file_name]\n        pred_list += [p.item() for p in preds_class]\n\nsubmission = pd.DataFrame({\"id\":file_name_list, \"label\":pred_list})\n#submission['label'] = submission.label.astype(int)\nsubmission['id'] = submission.id.str.split('.')\nsubmission['id'] = submission['id'].str[0]\nsubmission.to_csv('submission.csv', index=False)\nsubmission","metadata":{"id":"xrrGfOTyicO8","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}