{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":3362,"databundleVersionId":31148}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"т.к. дали .zip ","metadata":{}},{"cell_type":"code","source":"import zipfile\nimport os\n\n# Пути к архивам (проверьте, что пути в вашем Kaggle именно такие)\ntrain_zip = '/kaggle/input/competitions/dogs-vs-cats/train.zip'\ntest_zip = '/kaggle/input/competitions/dogs-vs-cats/test1.zip'\n\n# Распаковываем обучающую выборку\nwith zipfile.ZipFile(train_zip, 'r') as z:\n    z.extractall('/kaggle/working/')\n\n# Распаковываем тестовую выборку\nwith zipfile.ZipFile(test_zip, 'r') as z:\n    z.extractall('/kaggle/working/')\n\nprint(\"Файлы распакованы!\")\nprint(\"Примеры файлов в train:\", os.listdir('/kaggle/working/train')[:5])","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-03-03T12:00:18.090083Z","iopub.execute_input":"2026-03-03T12:00:18.090680Z","iopub.status.idle":"2026-03-03T12:00:26.704343Z","shell.execute_reply.started":"2026-03-03T12:00:18.090651Z","shell.execute_reply":"2026-03-03T12:00:26.703557Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls /kaggle/working/\n!echo '====='\n!ls /kaggle/working/test1 | head -n 15","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T12:10:05.117498Z","iopub.execute_input":"2026-03-03T12:10:05.118193Z","iopub.status.idle":"2026-03-03T12:10:05.556442Z","shell.execute_reply.started":"2026-03-03T12:10:05.118162Z","shell.execute_reply":"2026-03-03T12:10:05.555645Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import glob\nfrom PIL import Image\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as T\nfrom torchvision import models\nimport cv2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T12:00:27.174503Z","iopub.execute_input":"2026-03-03T12:00:27.175177Z","iopub.status.idle":"2026-03-03T12:00:27.940567Z","shell.execute_reply.started":"2026-03-03T12:00:27.175145Z","shell.execute_reply":"2026-03-03T12:00:27.940015Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Это для Train: добавляем случайные повороты и отражения\ntrain_transform = T.Compose([\n    T.Resize((224, 224)),\n    T.RandomHorizontalFlip(p=0.5),      # Случайное отзеркаливание\n    T.RandomRotation(degrees=15),       # Случайный поворот до 15 градусов\n    T.ToTensor(),\n    T.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225])\n])\n\n# А это для Test (тут искажать ничего нельзя!)\ntest_transform = T.Compose([\n    T.Resize((224, 224)),\n    T.ToTensor(),\n    T.Normalize([0.485, 0.456, 0.406],[0.229, 0.224, 0.225])\n])\n\n# в нормализацию можно закатать и 0.5 0.5 0.5","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T12:00:27.942152Z","iopub.execute_input":"2026-03-03T12:00:27.942404Z","iopub.status.idle":"2026-03-03T12:00:27.948795Z","shell.execute_reply.started":"2026-03-03T12:00:27.942382Z","shell.execute_reply":"2026-03-03T12:00:27.948198Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DogsVsCatsDataset(Dataset):\n    def __init__(self, file_paths, is_test=False):\n        self.file_paths = file_paths\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.file_paths)\n\n    def __getitem__(self, idx):\n        path = self.file_paths[idx]\n        # Читаем картинку и переводим в формат RGB\n        # БЫСТРОЕ ЧТЕНИЕ: Используем cv2 вместо PIL\n        image = cv2.imread(path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # OpenCV читает в BGR, переводим в RGB\n        image = Image.fromarray(image) # Отдаем в PIL, так как torchvision.transforms работает с ним\n\n        # можно вместо 3х предыдущих строк написать image = Image.open(path).convert('RGB')\n        \n        # Применяем трансформации\n        if self.is_test:\n            image = test_transform(image)\n        else:\n            image = train_transform(image)\n\n        if self.is_test:\n            # нужно внимательно узнать, как именно нам получить image_id\n            # В тесте нам нужно знать ID картинки для файла submission (например, из \"test1/15.jpg\" достаем 15)\n            img_id = int(path.split('/')[-1].split('.')[0])\n            return image, img_id\n\n        # нужно внимательно узнать, как именно нам получить image_id (тут например нужно самим определить label из названия картинки)\n        # Для трейна извлекаем метку из названия файла: \"cat.123.jpg\" -> \"cat\"\n        filename = path.split('/')[-1]\n        label = 1 if filename.startswith('dog') else 0 # 1 - собака, 0 - кошка\n        \n        return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T12:00:27.949687Z","iopub.execute_input":"2026-03-03T12:00:27.949938Z","iopub.status.idle":"2026-03-03T12:00:27.964687Z","shell.execute_reply.started":"2026-03-03T12:00:27.949903Z","shell.execute_reply":"2026-03-03T12:00:27.964146Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Получаем списки всех файлов\ntrain_files = glob.glob('/kaggle/working/train/*.jpg')\ntest_files = glob.glob('/kaggle/working/test1/*.jpg')\n\n# Для скорости обучения возьмем batch_size=32 (картинки большие, 64 может не влезть в память GPU)\ntrain_loader = DataLoader(DogsVsCatsDataset(train_files, is_test=False), batch_size=32, shuffle=True)\ntest_loader = DataLoader(DogsVsCatsDataset(test_files, is_test=True), batch_size=32, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T12:00:27.966348Z","iopub.execute_input":"2026-03-03T12:00:27.966602Z","iopub.status.idle":"2026-03-03T12:00:28.039542Z","shell.execute_reply.started":"2026-03-03T12:00:27.966584Z","shell.execute_reply":"2026-03-03T12:00:28.039026Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"### from torchvision import models\nimport torch.nn as nn\n\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\n\n# 1. Загружаем модель с УЖЕ обученными весами (если есть интернет или веса уже предзагружены в какой-нибудь файл)\n# AHTUNG!!! весов может и не быть(\n# model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\nmodel = models.resnet18(weights=None)\n\n# 2. conv1 и maxpool оставляем оригинальными! т.к. у нас картинки вполне подходят под формат модели\n\n# 3. Меняем только \"голову\" (последний слой) на 2 класса\nmodel.fc = nn.Linear(model.fc.in_features, 2)\nmodel = model.to(device)\n\noptimizer = torch.optim.Adam(model.parameters(), lr=0.0001) # Learning rate делаем меньше, т.к. модель уже предобучена\ncriterion = nn.CrossEntropyLoss()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T12:00:28.040365Z","iopub.execute_input":"2026-03-03T12:00:28.040605Z","iopub.status.idle":"2026-03-03T12:00:28.211433Z","shell.execute_reply.started":"2026-03-03T12:00:28.040574Z","shell.execute_reply":"2026-03-03T12:00:28.210663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for epoch in range(3):\n    model.train()\n    total_loss = 0\n    correct = 0\n    \n    for img, label in train_loader:\n        img, label = img.to(device), label.to(device)\n        \n        optimizer.zero_grad()\n        output = model(img)\n        loss = criterion(output, label)\n        \n        loss.backward()\n        optimizer.step()\n        \n        total_loss += loss.item()\n        correct += (output.argmax(1) == label).sum().item()\n        \n    acc = correct / len(train_files)\n    print(f\"Epoch {epoch+1} | Loss: {total_loss/len(train_loader):.4f} | Accuracy: {acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T12:00:28.212312Z","iopub.execute_input":"2026-03-03T12:00:28.212596Z","iopub.status.idle":"2026-03-03T12:08:04.578749Z","shell.execute_reply.started":"2026-03-03T12:00:28.212572Z","shell.execute_reply":"2026-03-03T12:08:04.577949Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\nall_preds = []\nall_ids =[]\nwith torch.no_grad():\n    for images, img_ids in test_loader:\n        images = images.to(device)\n        outputs = model(images)\n        preds = outputs.argmax(dim=1)\n        all_preds.extend(preds.cpu().numpy())\n        all_ids.extend(img_ids.numpy())\n\nsubmission = pd.DataFrame({\n    'id': all_ids,\n    'label': all_preds\n})\nsubmission = submission.sort_values(by='id')\nsubmission.to_csv('submission.csv', index=False)\nprint(submission.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T12:08:04.579851Z","iopub.execute_input":"2026-03-03T12:08:04.580177Z","iopub.status.idle":"2026-03-03T12:09:00.617518Z","shell.execute_reply.started":"2026-03-03T12:08:04.580153Z","shell.execute_reply":"2026-03-03T12:09:00.616863Z"}},"outputs":[],"execution_count":null}]}