{"metadata":{"accelerator":"GPU","colab":{"collapsed_sections":[],"name":"CV_md10.ipynb","provenance":[]},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":3362,"databundleVersionId":31148,"sourceType":"competition"}],"dockerImageVersionId":30699,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Классификация кошек и собак\n---\n\n### Загрузка библиотек","metadata":{"id":"KBKbWGdEXij9"}},{"cell_type":"code","source":"import os\nimport numpy as np\nimport random \nimport matplotlib.pyplot as plt\nimport time\nimport glob\n\nimport torch\nimport torch.nn as nn\nimport torchvision\nimport torchvision.models as M\nimport torchvision.transforms.v2 as TV2\nfrom torch.utils.data import Dataset, DataLoader\nimport cv2\n\n%matplotlib inline","metadata":{"id":"mhNjxPakXikE","execution":{"iopub.status.busy":"2024-05-23T17:38:42.976655Z","iopub.execute_input":"2024-05-23T17:38:42.977611Z","iopub.status.idle":"2024-05-23T17:38:49.390788Z","shell.execute_reply.started":"2024-05-23T17:38:42.977560Z","shell.execute_reply":"2024-05-23T17:38:49.389872Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Часть I. Предобученные сети как экстракторы признаков\t","metadata":{"id":"DafVDj54bB64"}},{"cell_type":"markdown","source":"### Загрузка данных","metadata":{"id":"mTp5OcrvXikK"}},{"cell_type":"code","source":"train_path = \"../input/dogs-vs-cats/train.zip\"\ntest_path = \"../input/dogs-vs-cats/test1.zip\"\n\nfiles = \"/kaggle/working/\"\n\nimport zipfile\n\nwith zipfile.ZipFile(train_path, 'r') as zipp:\n    zipp.extractall(files)\n    \nwith zipfile.ZipFile(test_path, 'r') as zipp:\n    zipp.extractall(files)","metadata":{"id":"mHqbMULbvrEU","execution":{"iopub.status.busy":"2024-05-23T17:38:56.302743Z","iopub.execute_input":"2024-05-23T17:38:56.303738Z","iopub.status.idle":"2024-05-23T17:39:09.927652Z","shell.execute_reply.started":"2024-05-23T17:38:56.303705Z","shell.execute_reply":"2024-05-23T17:39:09.926638Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_path = '/kaggle/working/train/'\ntest_path = '/kaggle/working/test1'\n\n# Получение списка файлов и директорий в указанной директории\ntrain_content = os.listdir(train_path)\ntest_content = os.listdir(test_path)\n\nprint(f'Число изображений в треннировочной выборке: {len(train_content)}')\nprint(f'Число изображений в тестовой выборке:       {len(test_content)}')","metadata":{"execution":{"iopub.status.busy":"2024-05-23T17:39:12.710575Z","iopub.execute_input":"2024-05-23T17:39:12.710969Z","iopub.status.idle":"2024-05-23T17:39:12.739203Z","shell.execute_reply.started":"2024-05-23T17:39:12.710939Z","shell.execute_reply":"2024-05-23T17:39:12.738250Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Мы создаем класс CustomDataset и передаем аргумент Dataset, \n# чтобы он мог наследовать функционал класса Torch Dataset\n\nclass CustomDataset(Dataset):\n    def __init__(self, train_flag='train', my_path=train_path, transform=None):\n        self.imgs_path = my_path\n        self.data = []\n        self.transform = transform\n        \n        # Мы добавляем путь к файлу для каждого изображения в список \n        # self.data вместе с соответствующим именем класса. \n        for img_path in glob.glob(self.imgs_path + \"/*.jpg\"):\n            class_name = img_path.split(\".\")[0].split(\"/\")[-1]\n            self.data.append([img_path, class_name])\n        \n        self.classes = ['dog', 'cat']\n        self.class_map = {\"dog\" : 0, \"cat\": 1}\n        self.img_dim = (224, 224)\n        # Перемешаем данные\n        random.shuffle(self.data) \n            \n        # Разделим выборку на обучающую и валидационную\n        if train_flag == 'train':\n            self.data = self.data[:20000]\n        elif train_flag == 'val':\n            self.data = self.data[20000:]\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        img_path, class_name = self.data[idx]\n        # Мы используем opencv, чтобы загрузить изображение и\n        # изменить его размер до необходимого\n        img = cv2.imread(img_path)\n        img = cv2.resize(img, self.img_dim)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        # Словарь self.class_map обеспечивает отображение имени класса в метку.\n        class_id = self.class_map[class_name]\n        img_tensor = torch.from_numpy(img)\n        img_tensor = img_tensor.permute(2, 0, 1)\n        # Произведем скейлинг изображения: переведем пиксели от 0 до 255 к формату от 0 до 1 \n        img_tensor = img_tensor / 255\n        # Если задан трансформер, то правим изображение\n        if self.transform:\n            img_tensor = self.transform(img_tensor)\n\n        class_id = torch.tensor([class_id])\n        \n        return img_tensor, class_id","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:36:26.385987Z","iopub.execute_input":"2024-05-23T18:36:26.387134Z","iopub.status.idle":"2024-05-23T18:36:26.399958Z","shell.execute_reply.started":"2024-05-23T18:36:26.387090Z","shell.execute_reply":"2024-05-23T18:36:26.399026Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Параметр **train_flag** класса CustomDataset позволяет нам выбирать между обучающей и тестовой выборкой.","metadata":{"id":"thygXAHZzKSb"}},{"cell_type":"code","source":"train_data = CustomDataset()\nval_data = CustomDataset('val')\n\nBATCH_SIZE = 8\n\ntrain_loader = DataLoader(train_data, batch_size=BATCH_SIZE, shuffle=True)\nval_loader = DataLoader(val_data, batch_size=BATCH_SIZE, shuffle=True)","metadata":{"id":"RTM5ldJT2lzb","outputId":"57c90eb7-9c8d-4100-a70f-cd3bf0f84849","execution":{"iopub.status.busy":"2024-05-23T17:39:21.637047Z","iopub.execute_input":"2024-05-23T17:39:21.637392Z","iopub.status.idle":"2024-05-23T17:39:22.069226Z","shell.execute_reply.started":"2024-05-23T17:39:21.637366Z","shell.execute_reply":"2024-05-23T17:39:22.068384Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\nПосмотрим на размер train_data с помощью метода __len__().","metadata":{"id":"PZtdgwCJFhz3"}},{"cell_type":"code","source":"print('Train data len:', train_data.__len__())\nprint('Valid data len:', val_data.__len__())","metadata":{"id":"oyaN2dYcft2G","outputId":"602c0132-4f0d-4a8c-eaa7-37b49e5b145f","execution":{"iopub.status.busy":"2024-05-23T17:39:25.541803Z","iopub.execute_input":"2024-05-23T17:39:25.542178Z","iopub.status.idle":"2024-05-23T17:39:25.546956Z","shell.execute_reply.started":"2024-05-23T17:39:25.542149Z","shell.execute_reply":"2024-05-23T17:39:25.546087Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Отрисуем один батч картинок, загруженных с помощью DataLoader()","metadata":{"id":"69m7lSThF3xv"}},{"cell_type":"code","source":"X_train, y_train = next(iter(train_loader))\nimg = torchvision.utils.make_grid(X_train)\nimg = img.numpy().transpose((1,2,0))","metadata":{"id":"8fIaBTDS2p7O","execution":{"iopub.status.busy":"2024-05-23T17:39:27.750495Z","iopub.execute_input":"2024-05-23T17:39:27.751368Z","iopub.status.idle":"2024-05-23T17:39:27.873972Z","shell.execute_reply.started":"2024-05-23T17:39:27.751334Z","shell.execute_reply":"2024-05-23T17:39:27.873135Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"classes = train_data.classes\nclasses","metadata":{"id":"ra8i7J0C3Zd0","outputId":"95e8d29c-f688-4db3-d45e-61425a49e067","execution":{"iopub.status.busy":"2024-05-23T17:39:30.695407Z","iopub.execute_input":"2024-05-23T17:39:30.696110Z","iopub.status.idle":"2024-05-23T17:39:30.704088Z","shell.execute_reply.started":"2024-05-23T17:39:30.696071Z","shell.execute_reply":"2024-05-23T17:39:30.703036Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print([classes[i] for i in y_train])\nplt.imshow(img);","metadata":{"id":"oLUf_KI22stz","outputId":"0cf034ae-f0f1-415a-bdaa-0afec861c64c","execution":{"iopub.status.busy":"2024-05-23T17:39:33.930233Z","iopub.execute_input":"2024-05-23T17:39:33.930579Z","iopub.status.idle":"2024-05-23T17:39:34.256552Z","shell.execute_reply.started":"2024-05-23T17:39:33.930551Z","shell.execute_reply":"2024-05-23T17:39:34.255593Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Загрузка модели","metadata":{"id":"MpVLz7GZ8gAM"}},{"cell_type":"markdown","source":"Мы будем использовать VGG16 в качестве предобученной сети.","metadata":{"id":"i5CcFZd-niDD"}},{"cell_type":"code","source":"model = M.vgg16(weights=M.VGG16_Weights.IMAGENET1K_V1)\nprint(model)","metadata":{"id":"TH0Kb8ajAPTZ","outputId":"664ff9e9-3212-4777-d2b3-8fc7a7bdcd45","execution":{"iopub.status.busy":"2024-05-23T17:39:48.207879Z","iopub.execute_input":"2024-05-23T17:39:48.208242Z","iopub.status.idle":"2024-05-23T17:39:58.312280Z","shell.execute_reply.started":"2024-05-23T17:39:48.208214Z","shell.execute_reply":"2024-05-23T17:39:58.311405Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Это значит, что мы будем использовать уже обученные веса данной нейронной сети.\n\nНапомним, что веса можно получить в виде словаря \"состояния\" сети:","metadata":{"id":"YOEeksDEoGK6"}},{"cell_type":"code","source":"# model.state_dict()","metadata":{"id":"qzTH2pwUneIi","execution":{"iopub.status.busy":"2024-05-23T11:31:25.935263Z","iopub.execute_input":"2024-05-23T11:31:25.936009Z","iopub.status.idle":"2024-05-23T11:31:25.940447Z","shell.execute_reply.started":"2024-05-23T11:31:25.935974Z","shell.execute_reply":"2024-05-23T11:31:25.939275Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"total_params = sum(p.numel() for p in model.parameters())\nprint(\"Total parameters: \", total_params)","metadata":{"execution":{"iopub.status.busy":"2024-05-23T17:40:08.753326Z","iopub.execute_input":"2024-05-23T17:40:08.753681Z","iopub.status.idle":"2024-05-23T17:40:08.759603Z","shell.execute_reply.started":"2024-05-23T17:40:08.753653Z","shell.execute_reply":"2024-05-23T17:40:08.758548Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Заморозка слоев VGG16 сети","metadata":{"id":"--o8IWbw_ue8"}},{"cell_type":"code","source":"# Помним, что заморозка предполагает фиксацию весов модели во время обратного распространения ошибки \nfor param in model.parameters():\n    param.requires_grad = False\n\n# Посмотрим, сколько нейронов подается на вход выходному слою классификатора\nn_neurons = model.classifier[-1].in_features \nprint(n_neurons)\n\n# Заменим этот слой на новый, выходное количество нейронов которого соотвествует нашей задачи бинарной классификации\nnum_classes = 2\nmodel.classifier[-1] = nn.Linear(n_neurons, num_classes)","metadata":{"id":"15iC_qem_z2A","outputId":"f31463d4-9426-471c-e00a-7191aab0b106","execution":{"iopub.status.busy":"2024-05-23T17:40:14.574460Z","iopub.execute_input":"2024-05-23T17:40:14.574907Z","iopub.status.idle":"2024-05-23T17:40:14.583042Z","shell.execute_reply.started":"2024-05-23T17:40:14.574873Z","shell.execute_reply":"2024-05-23T17:40:14.581999Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.classifier","metadata":{"id":"hoeJithE-oyN","outputId":"55c92aa2-a26e-4158-8d06-ba81d01b39c2","execution":{"iopub.status.busy":"2024-05-23T17:40:19.381709Z","iopub.execute_input":"2024-05-23T17:40:19.382079Z","iopub.status.idle":"2024-05-23T17:40:19.388192Z","shell.execute_reply.started":"2024-05-23T17:40:19.382051Z","shell.execute_reply":"2024-05-23T17:40:19.387231Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Будем обучать только добавленный выходной слой классификатора\nfor index, param in enumerate(model.classifier.parameters()):\n    if index == 6:\n        param.requires_grad = True","metadata":{"id":"CTUZ0JYLEFLM","execution":{"iopub.status.busy":"2024-05-23T17:40:22.363685Z","iopub.execute_input":"2024-05-23T17:40:22.364025Z","iopub.status.idle":"2024-05-23T17:40:22.368832Z","shell.execute_reply.started":"2024-05-23T17:40:22.364001Z","shell.execute_reply":"2024-05-23T17:40:22.367945Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"use_gpu = torch.cuda.is_available()\nprint(use_gpu)\n\nif use_gpu:\n    device = 'cuda'\nelse:\n    device = 'cpu'\n\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.classifier.parameters())","metadata":{"id":"gdJ-Vo0SErek","outputId":"ef7a7afa-9603-40d9-f74b-7239d94d6b3e","execution":{"iopub.status.busy":"2024-05-23T17:40:24.982452Z","iopub.execute_input":"2024-05-23T17:40:24.983124Z","iopub.status.idle":"2024-05-23T17:40:25.297988Z","shell.execute_reply.started":"2024-05-23T17:40:24.983096Z","shell.execute_reply":"2024-05-23T17:40:25.296999Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.classifier","metadata":{"id":"v4-87lTFFH0s","outputId":"ea658574-e3c3-4668-8067-e53f16a37260","execution":{"iopub.status.busy":"2024-05-23T17:40:41.471769Z","iopub.execute_input":"2024-05-23T17:40:41.472698Z","iopub.status.idle":"2024-05-23T17:40:41.481462Z","shell.execute_reply.started":"2024-05-23T17:40:41.472661Z","shell.execute_reply":"2024-05-23T17:40:41.480587Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Обучение модели","metadata":{"id":"w_UWUIjTGnqU"}},{"cell_type":"markdown","source":"Для удобства создадим словарь даталоадеров:","metadata":{"id":"ucVbH1ZMMuqF"}},{"cell_type":"code","source":"dataloaders = {\n    'train': train_loader,\n    'val': val_loader\n}","metadata":{"id":"gak49VhHM01R","execution":{"iopub.status.busy":"2024-05-23T17:40:46.727825Z","iopub.execute_input":"2024-05-23T17:40:46.728238Z","iopub.status.idle":"2024-05-23T17:40:46.732723Z","shell.execute_reply.started":"2024-05-23T17:40:46.728211Z","shell.execute_reply":"2024-05-23T17:40:46.731756Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train_model(\n    model, device, dataloaders, criterion,\n    optimizer, batch_size, num_epochs=1, rep_freq=None):\n    since = time.time()\n\n    for epoch in range(num_epochs):\n        print('Epoch {}/{}'.format(epoch, num_epochs - 1))\n        print('-' * 10)\n\n        # На каждой эпохе происходит обучение и валидая \n        for phase in ['train', 'val']:\n            if phase == 'train':\n                model.train() \n            else:\n                model.eval() \n            \n            current_loss = 0.0\n            current_corrects = 0\n            batch = 0\n\n            # Проходим по данным\n            for inputs, labels in dataloaders[phase]:\n                batch += 1\n                inputs = inputs.to(device)\n                labels = labels.to(device)\n\n                # обнуляем градиенты\n                optimizer.zero_grad()\n\n                with torch.set_grad_enabled(phase == 'train'):\n                    # Получаем прогноз модели и считаем лосс\n                    outputs = model(inputs)\n                    _, preds = torch.max(outputs, 1)\n\n                    labels = labels.squeeze(-1)\n                    loss = criterion(outputs, labels)\n\n                    if phase == 'train':\n                        loss.backward()\n                        optimizer.step()\n\n                # считаем статистики\n                current_loss += loss.item() * inputs.size(0)\n                current_corrects += torch.sum(preds == labels)\n                \n                # вывод отчета о динамике с указанной частотой\n                if rep_freq:\n                    if batch%rep_freq == 0 and phase == \"train\":\n                        print(\"Batch {}, Train Loss:{:.4f}, Train ACC:{:.4f}\".format(\n                            batch, current_loss/(batch_size*batch),\n                            100*current_corrects/(batch_size*batch)))\n\n            epoch_loss = current_loss / CustomDataset(phase).__len__()\n            epoch_acc = current_corrects.double() / CustomDataset(phase).__len__()\n\n            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))\n            \n    time_elapsed = time.time() - since\n    print('Время обучения: {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))\n    return model","metadata":{"id":"CIMpojG3GlCH","execution":{"iopub.status.busy":"2024-05-23T17:40:52.035625Z","iopub.execute_input":"2024-05-23T17:40:52.036360Z","iopub.status.idle":"2024-05-23T17:40:52.048660Z","shell.execute_reply.started":"2024-05-23T17:40:52.036331Z","shell.execute_reply":"2024-05-23T17:40:52.047545Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cat_vs_dog = train_model(\n    model, device, dataloaders, criterion,\n    optimizer, batch_size=BATCH_SIZE,\n    num_epochs=1, rep_freq=512\n)","metadata":{"id":"j2vxja6RMtoS","outputId":"122b1a9e-adbe-4944-f3de-0c57f9c294dd","execution":{"iopub.status.busy":"2024-05-23T17:40:56.103512Z","iopub.execute_input":"2024-05-23T17:40:56.103902Z","iopub.status.idle":"2024-05-23T17:43:17.257988Z","shell.execute_reply.started":"2024-05-23T17:40:56.103875Z","shell.execute_reply":"2024-05-23T17:43:17.256983Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Визуализация модели","metadata":{"id":"Q9q4UrHBsztd"}},{"cell_type":"code","source":"def visualize_model(model, num_images=6):\n    was_training = model.training\n    model.eval()\n    images_handeled = 0\n    _ = plt.figure()\n\n    if use_gpu:\n        device = 'cuda'\n    else:\n        device = 'cpu'\n\n    with torch.no_grad():\n        for i, (inputs, labels) in enumerate(dataloaders['val']):\n            inputs = inputs.to(device)\n            labels = labels.to(device)\n\n            outputs = model(inputs)\n            _, preds = torch.max(outputs, 1)\n\n            for j in range(inputs.size()[0]):\n                images_handeled += 1\n                ax = plt.subplot(num_images//2, 2, images_handeled)\n                ax.axis('off')\n                ax.set_title('predicted: {}'.format(classes[preds[j]]))\n                img = inputs.cpu().data[j]\n                img = img.numpy().transpose((1, 2, 0))\n                plt.imshow(img)\n\n                if images_handeled == num_images:\n                    model.train(mode=was_training)\n                    return\n        model.train(mode=was_training)","metadata":{"id":"nq9wIDBps4tm","execution":{"iopub.status.busy":"2024-05-23T17:43:55.996552Z","iopub.execute_input":"2024-05-23T17:43:55.996915Z","iopub.status.idle":"2024-05-23T17:43:56.006878Z","shell.execute_reply.started":"2024-05-23T17:43:55.996888Z","shell.execute_reply":"2024-05-23T17:43:56.005869Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"visualize_model(cat_vs_dog)\nplt.show()","metadata":{"id":"EtmdNPaItBFR","outputId":"0074ed6c-ee25-4f48-893b-b120418ad800","execution":{"iopub.status.busy":"2024-05-23T17:43:59.053863Z","iopub.execute_input":"2024-05-23T17:43:59.054535Z","iopub.status.idle":"2024-05-23T17:43:59.524995Z","shell.execute_reply.started":"2024-05-23T17:43:59.054505Z","shell.execute_reply":"2024-05-23T17:43:59.524126Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"-----","metadata":{"id":"efc02o1sa-EK"}},{"cell_type":"markdown","source":"## Часть II. Тонкая настройка модели","metadata":{"id":"u83EDVJba_ar"}},{"cell_type":"code","source":"train_data_ft = CustomDataset()\nval_data_ft = CustomDataset('val')\n\nBATCH_SIZE = 16\n\ntrain_loader_ft = DataLoader(\n    train_data_ft, batch_size=BATCH_SIZE, shuffle=True\n)\nval_loader_ft = DataLoader(\n    val_data_ft, batch_size=BATCH_SIZE, shuffle=True\n)","metadata":{"id":"A69CDq4Ka-zY","outputId":"d547e1ee-ab03-474c-830f-1aaa783c158b","execution":{"iopub.status.busy":"2024-05-23T17:44:06.280499Z","iopub.execute_input":"2024-05-23T17:44:06.280896Z","iopub.status.idle":"2024-05-23T17:44:06.570710Z","shell.execute_reply.started":"2024-05-23T17:44:06.280867Z","shell.execute_reply":"2024-05-23T17:44:06.569713Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Отрисуем один батч картинок, загруженных с помощью DataLoader()","metadata":{"id":"kCDcvoN8dZ2S"}},{"cell_type":"code","source":"def imshow(inp, title=None): \n    \"\"\"Imshow for Tensor.\"\"\"\n    inp = inp.numpy().transpose((1, 2, 0)) \n    plt.figure(figsize=(15,10)) \n    plt.imshow(inp) ","metadata":{"id":"RIerrR6U0mS_","execution":{"iopub.status.busy":"2024-05-23T17:44:10.021152Z","iopub.execute_input":"2024-05-23T17:44:10.022005Z","iopub.status.idle":"2024-05-23T17:44:10.027536Z","shell.execute_reply.started":"2024-05-23T17:44:10.021973Z","shell.execute_reply":"2024-05-23T17:44:10.026461Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train_ft, y_train_ft = next(iter(train_loader_ft))\nimg = torchvision.utils.make_grid(X_train_ft, scale_each=True)\nimshow(img)","metadata":{"id":"_SaozBwUddHo","outputId":"a01fea0f-f19a-4315-c73a-7d286c786205","execution":{"iopub.status.busy":"2024-05-23T17:44:14.926026Z","iopub.execute_input":"2024-05-23T17:44:14.926627Z","iopub.status.idle":"2024-05-23T17:44:15.636361Z","shell.execute_reply.started":"2024-05-23T17:44:14.926596Z","shell.execute_reply":"2024-05-23T17:44:15.635403Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Инициализируем модель","metadata":{"id":"1eD66HuDdwoq"}},{"cell_type":"code","source":"model = M.vgg16(weights=M.VGG16_Weights.IMAGENET1K_V1)","metadata":{"id":"tINgPfT0dj9h","outputId":"959a5794-2996-4e5d-dcbe-d5e715b22524","execution":{"iopub.status.busy":"2024-05-23T17:44:33.496753Z","iopub.execute_input":"2024-05-23T17:44:33.497129Z","iopub.status.idle":"2024-05-23T17:44:35.229022Z","shell.execute_reply.started":"2024-05-23T17:44:33.497102Z","shell.execute_reply":"2024-05-23T17:44:35.228222Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Настройка слоев VGG16 сети","metadata":{"id":"hVq6cO4Ad36I"}},{"cell_type":"code","source":"# Посмотрим, сколько нейронов подается на вход выходному слою классификатора\nn_neurons = model.classifier[-1].in_features \nprint(n_neurons)\n\n# Заменим этот слой на новый, выходное количество нейронов которого соотвествует нашей задачи бинарной классификации\nnum_classes = 2\nmodel.classifier[-1] = nn.Linear(n_neurons, num_classes)","metadata":{"id":"g5eO7HUqd4dm","outputId":"13d05d1c-eb25-4f8a-f261-567ebbf7cc4a","execution":{"iopub.status.busy":"2024-05-23T17:44:38.596946Z","iopub.execute_input":"2024-05-23T17:44:38.597792Z","iopub.status.idle":"2024-05-23T17:44:38.604378Z","shell.execute_reply.started":"2024-05-23T17:44:38.597758Z","shell.execute_reply":"2024-05-23T17:44:38.603393Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Настроим 3 последних сверточных слоя сети\nfor index, param in enumerate(model.features.parameters()):\n    if index >= 24:\n        param.requires_grad = True\n    else:\n        param.requires_grad = False","metadata":{"id":"2hXtvbbjeJIq","execution":{"iopub.status.busy":"2024-05-23T17:44:41.803891Z","iopub.execute_input":"2024-05-23T17:44:41.804256Z","iopub.status.idle":"2024-05-23T17:44:41.809420Z","shell.execute_reply.started":"2024-05-23T17:44:41.804229Z","shell.execute_reply":"2024-05-23T17:44:41.808522Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"use_gpu = torch.cuda.is_available()\nprint(use_gpu)\n\nif use_gpu:\n      device = 'cuda'\nelse:\n      device = 'cpu'\n\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters())","metadata":{"id":"-sYzvFEfeLo0","outputId":"46543370-c4bb-4ae6-8291-01bdac3c20bf","execution":{"iopub.status.busy":"2024-05-23T17:44:44.542735Z","iopub.execute_input":"2024-05-23T17:44:44.543726Z","iopub.status.idle":"2024-05-23T17:44:44.707577Z","shell.execute_reply.started":"2024-05-23T17:44:44.543689Z","shell.execute_reply":"2024-05-23T17:44:44.706545Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Обучение модели","metadata":{"id":"ItrJjmNJeHJT"}},{"cell_type":"code","source":"dataloaders_ft = {\n    'train': train_loader_ft,\n    'val': val_loader_ft\n}","metadata":{"id":"hfdG7YWnePkc","execution":{"iopub.status.busy":"2024-05-23T17:44:47.285000Z","iopub.execute_input":"2024-05-23T17:44:47.285408Z","iopub.status.idle":"2024-05-23T17:44:47.290136Z","shell.execute_reply.started":"2024-05-23T17:44:47.285377Z","shell.execute_reply":"2024-05-23T17:44:47.288896Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cat_vs_dog_finetuned = train_model(\n    model, device, dataloaders_ft, criterion,\n    optimizer, batch_size=BATCH_SIZE,\n    num_epochs=1, rep_freq=512\n)","metadata":{"id":"lJKVtuuzeX-W","outputId":"e66f2bc3-d30a-41ef-8aaa-84105e0284d4","execution":{"iopub.status.busy":"2024-05-23T17:44:49.954586Z","iopub.execute_input":"2024-05-23T17:44:49.955337Z","iopub.status.idle":"2024-05-23T17:47:22.839016Z","shell.execute_reply.started":"2024-05-23T17:44:49.955304Z","shell.execute_reply":"2024-05-23T17:47:22.838036Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Визуализация модели","metadata":{"id":"I9UQ9Iigeeki"}},{"cell_type":"code","source":"visualize_model(cat_vs_dog_finetuned)\nplt.show()","metadata":{"id":"e-VJRUq6ehu2","outputId":"938ef440-c5e4-446e-a927-8125409b5faa","execution":{"iopub.status.busy":"2024-05-23T17:47:48.526624Z","iopub.execute_input":"2024-05-23T17:47:48.527014Z","iopub.status.idle":"2024-05-23T17:47:49.061476Z","shell.execute_reply.started":"2024-05-23T17:47:48.526987Z","shell.execute_reply":"2024-05-23T17:47:49.060506Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Часть III. Аугментация\n\nДобавим аугментацию данных.","metadata":{"id":"FU6co14RekB8"}},{"cell_type":"code","source":"transformer = TV2.Compose([\n    TV2.ToDtype(torch.uint8, scale=True),\n    TV2.RandomHorizontalFlip(),\n    TV2.ColorJitter(\n        brightness=0.5, hue=0.3\n    ),\n#     TV2.RandomSolarize(threshold=210.0),\n    TV2.ToDtype(torch.float32, scale=True),\n#     TV2.Normalize(\n#         mean=[0.485, 0.456, 0.406],\n#         std=[0.229, 0.224, 0.225]\n#     )\n])","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:00:54.627929Z","iopub.execute_input":"2024-05-23T18:00:54.628689Z","iopub.status.idle":"2024-05-23T18:00:54.634116Z","shell.execute_reply.started":"2024-05-23T18:00:54.628657Z","shell.execute_reply":"2024-05-23T18:00:54.633098Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data_ft_aug = CustomDataset(transform=transformer)\nval_data = CustomDataset('val')\n\nBATCH_SIZE = 16\n\ntrain_loader_ft_aug = DataLoader(\n    train_data_ft_aug, batch_size=BATCH_SIZE, shuffle=True\n)\nval_loader = DataLoader(\n    val_data, batch_size=BATCH_SIZE, shuffle=True\n)","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:00:57.122356Z","iopub.execute_input":"2024-05-23T18:00:57.123014Z","iopub.status.idle":"2024-05-23T18:00:57.638089Z","shell.execute_reply.started":"2024-05-23T18:00:57.122982Z","shell.execute_reply":"2024-05-23T18:00:57.637048Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train_ft_aug, y_train_ft_aug = next(iter(train_loader_ft_aug))\nimg = torchvision.utils.make_grid(X_train_ft_aug, scale_each=True)\nimshow(img)","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:01:00.595983Z","iopub.execute_input":"2024-05-23T18:01:00.596323Z","iopub.status.idle":"2024-05-23T18:01:01.379637Z","shell.execute_reply.started":"2024-05-23T18:01:00.596296Z","shell.execute_reply":"2024-05-23T18:01:01.378697Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = M.vgg16(weights=M.VGG16_Weights.IMAGENET1K_V1)\n\nn_neurons = model.classifier[-1].in_features \nprint(n_neurons)\n\nnum_classes = 2\nmodel.classifier[-1] = nn.Linear(n_neurons, num_classes)\n\nfor index, param in enumerate(model.features.parameters()):\n    if index >= 24:\n        param.requires_grad = True\n    else:\n        param.requires_grad = False\n        \nuse_gpu = torch.cuda.is_available()\nprint(use_gpu)\n\nif use_gpu:\n      device = 'cuda'\nelse:\n      device = 'cpu'\n\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters())","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:01:11.352715Z","iopub.execute_input":"2024-05-23T18:01:11.353655Z","iopub.status.idle":"2024-05-23T18:01:13.207669Z","shell.execute_reply.started":"2024-05-23T18:01:11.353619Z","shell.execute_reply":"2024-05-23T18:01:13.206647Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dataloaders_ft_aug = {\n    'train': train_loader_ft_aug,\n    'val': val_loader\n}","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:01:17.556653Z","iopub.execute_input":"2024-05-23T18:01:17.557539Z","iopub.status.idle":"2024-05-23T18:01:17.569539Z","shell.execute_reply.started":"2024-05-23T18:01:17.557508Z","shell.execute_reply":"2024-05-23T18:01:17.568413Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cat_vs_dog_finetuned_augmentation = train_model(\n    model, device, dataloaders_ft_aug, criterion,\n    optimizer, batch_size=BATCH_SIZE,\n    num_epochs=1, rep_freq=512\n)","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:01:20.472246Z","iopub.execute_input":"2024-05-23T18:01:20.472624Z","iopub.status.idle":"2024-05-23T18:05:42.815701Z","shell.execute_reply.started":"2024-05-23T18:01:20.472585Z","shell.execute_reply":"2024-05-23T18:05:42.814855Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Каким-то чудом точность на валидационной выборке чуток подросла: ранее различные комбинации аугментаций сказывались негативно, но это, видимо, мне еще не хватает пользовательского опыта.","metadata":{}},{"cell_type":"code","source":"visualize_model(cat_vs_dog_finetuned_augmentation)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:07:50.479611Z","iopub.execute_input":"2024-05-23T18:07:50.480462Z","iopub.status.idle":"2024-05-23T18:07:51.012902Z","shell.execute_reply.started":"2024-05-23T18:07:50.480430Z","shell.execute_reply":"2024-05-23T18:07:51.011885Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Часть IV. Замена классификатора\n\nПопробуем изменить модель путем полной замены полносвязного классификатора на новый.","metadata":{}},{"cell_type":"code","source":"train_data_new_clf = CustomDataset()\nval_data_new_clf = CustomDataset('val')\n\nBATCH_SIZE = 16\n\ntrain_loader_new_clf = DataLoader(\n    train_data_new_clf, batch_size=BATCH_SIZE, shuffle=True\n)\nval_loader_new_clf = DataLoader(\n    val_data_new_clf, batch_size=BATCH_SIZE, shuffle=True\n)","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:08:08.844292Z","iopub.execute_input":"2024-05-23T18:08:08.845311Z","iopub.status.idle":"2024-05-23T18:08:09.148537Z","shell.execute_reply.started":"2024-05-23T18:08:08.845274Z","shell.execute_reply":"2024-05-23T18:08:09.147781Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train_new_clf, y_train_new_clf = next(iter(train_loader_new_clf))\nimg = torchvision.utils.make_grid(X_train_new_clf, scale_each=True)\nimshow(img)","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:08:11.485260Z","iopub.execute_input":"2024-05-23T18:08:11.485611Z","iopub.status.idle":"2024-05-23T18:08:12.211374Z","shell.execute_reply.started":"2024-05-23T18:08:11.485584Z","shell.execute_reply":"2024-05-23T18:08:12.210472Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = M.vgg16(weights=M.VGG16_Weights.IMAGENET1K_V1)\n\nmodel.classifier","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:08:20.197641Z","iopub.execute_input":"2024-05-23T18:08:20.198012Z","iopub.status.idle":"2024-05-23T18:08:21.865465Z","shell.execute_reply.started":"2024-05-23T18:08:20.197982Z","shell.execute_reply":"2024-05-23T18:08:21.864484Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.classifier = nn.Sequential(\n    nn.Flatten(),\n    nn.Linear(25088, 4096),\n    nn.ReLU(inplace=True),\n    nn.BatchNorm1d(4096),  # Добавим батч-нормализацию\n    nn.Linear(4096, 1024),\n    nn.ReLU(inplace=True),\n    nn.BatchNorm1d(1024),  # Добавим батч-нормализацию\n    nn.Linear(1024, out_features=2)\n)","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:08:27.579052Z","iopub.execute_input":"2024-05-23T18:08:27.579426Z","iopub.status.idle":"2024-05-23T18:08:28.612370Z","shell.execute_reply.started":"2024-05-23T18:08:27.579394Z","shell.execute_reply":"2024-05-23T18:08:28.611588Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for index, param in enumerate(model.features.parameters()):\n    if index >= 24:\n        param.requires_grad = True\n    else:\n        param.requires_grad = False\n        \nuse_gpu = torch.cuda.is_available()\nprint(use_gpu)\n\nif use_gpu:\n      device = 'cuda'\nelse:\n      device = 'cpu'\n\nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters())","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:08:57.963876Z","iopub.execute_input":"2024-05-23T18:08:57.964713Z","iopub.status.idle":"2024-05-23T18:08:58.107118Z","shell.execute_reply.started":"2024-05-23T18:08:57.964683Z","shell.execute_reply":"2024-05-23T18:08:58.106204Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dataloaders_new_clf = {\n    'train': train_loader_new_clf,\n    'val': val_loader_new_clf\n}","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:09:01.315815Z","iopub.execute_input":"2024-05-23T18:09:01.316536Z","iopub.status.idle":"2024-05-23T18:09:01.320554Z","shell.execute_reply.started":"2024-05-23T18:09:01.316505Z","shell.execute_reply":"2024-05-23T18:09:01.319604Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cat_vs_dog_new_clf = train_model(\n    model, device, dataloaders_new_clf, criterion,\n    optimizer, batch_size=BATCH_SIZE,\n    num_epochs=1, rep_freq=512\n)","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:09:05.100725Z","iopub.execute_input":"2024-05-23T18:09:05.101160Z","iopub.status.idle":"2024-05-23T18:11:33.992011Z","shell.execute_reply.started":"2024-05-23T18:09:05.101130Z","shell.execute_reply":"2024-05-23T18:11:33.990804Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Похоже, дефолтный классификатор можно полностью подменить без ущерба для качества.","metadata":{}},{"cell_type":"code","source":"visualize_model(cat_vs_dog_new_clf)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-05-23T18:13:08.459861Z","iopub.execute_input":"2024-05-23T18:13:08.460224Z","iopub.status.idle":"2024-05-23T18:13:08.980595Z","shell.execute_reply.started":"2024-05-23T18:13:08.460197Z","shell.execute_reply":"2024-05-23T18:13:08.979664Z"},"trusted":true},"execution_count":null,"outputs":[]}]}