{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","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":"nvidiaTeslaT4","dataSources":[{"sourceId":96000,"databundleVersionId":11397519,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":12553005,"sourceType":"datasetVersion","datasetId":7926067}],"dockerImageVersionId":30749,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\n\nimport torch\nimport torch.nn as nn\nfrom torchvision import transforms, models\nimport torchvision.transforms.functional as F\nimport torch.nn.functional as Func\n\nfrom torchvision.datasets import ImageFolder\nfrom torch.utils.data import Dataset, DataLoader, ConcatDataset, Subset\n\nimport os\nimport gc\nimport shutil\nimport random\nfrom tqdm import tqdm\n\nfrom PIL import Image\n\nfrom sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay, accuracy_score, precision_score, recall_score, f1_score, roc_curve, auc\nfrom sklearn.preprocessing import label_binarize\n\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nplt.rcParams.update({\n    'figure.figsize': (16, 8),    \n    'figure.facecolor': 'white',    \n    'figure.autolayout': True,     \n})\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\ngpu_count = torch.cuda.device_count()\nbatch_size = 32","metadata":{"_uuid":"051d70d956493feee0c6d64651c6a088724dca2a","_execution_state":"idle","trusted":true,"execution":{"iopub.status.busy":"2025-07-23T20:25:00.359277Z","iopub.execute_input":"2025-07-23T20:25:00.359920Z","iopub.status.idle":"2025-07-23T20:25:06.804021Z","shell.execute_reply.started":"2025-07-23T20:25:00.359877Z","shell.execute_reply":"2025-07-23T20:25:06.803292Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"SEED = 42\nrandom.seed(SEED)\ntorch.manual_seed(SEED)\nnp.random.seed(SEED)\nif device:\n    torch.cuda.manual_seed(SEED)\n    torch.cuda.manual_seed_all(SEED)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T20:25:06.805442Z","iopub.execute_input":"2025-07-23T20:25:06.805817Z","iopub.status.idle":"2025-07-23T20:25:06.814109Z","shell.execute_reply.started":"2025-07-23T20:25:06.805795Z","shell.execute_reply":"2025-07-23T20:25:06.813281Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def sorted_classes(path):\n    folders = os.listdir(path)\n    sorted_folders = sorted(folders, key=lambda x: int(x.split('_')[-1]))\n    return sorted_folders","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T09:31:01.213470Z","iopub.execute_input":"2025-07-23T09:31:01.214195Z","iopub.status.idle":"2025-07-23T09:31:01.219052Z","shell.execute_reply.started":"2025-07-23T09:31:01.214153Z","shell.execute_reply":"2025-07-23T09:31:01.218313Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_dir = \"/kaggle/input/butterflies-classification/train_butterflies/train_split\"\nclass_names = sorted_classes(data_dir)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T09:31:03.578703Z","iopub.execute_input":"2025-07-23T09:31:03.579320Z","iopub.status.idle":"2025-07-23T09:31:03.588689Z","shell.execute_reply.started":"2025-07-23T09:31:03.579293Z","shell.execute_reply":"2025-07-23T09:31:03.588065Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_counts = {\n    class_name: len(os.listdir(os.path.join(data_dir, class_name)))\n    for class_name in class_names\n}\n\ndf = pd.DataFrame(\n    list(class_counts.items()),\n    columns=[\"class\", \"count\"]\n)\n\nsns.barplot(data=df, x=\"class\", y=\"count\", palette=\"viridis\")\nplt.xticks(rotation=45)\nplt.title(\"Распределение изображений по классам\", pad=20)\nplt.xlabel(\"class\")\nplt.ylabel(\"count\")\n\nmean_val = df[\"count\"].mean()\nplt.axhline(mean_val, color=\"red\", linestyle=\"--\")\n\nplt.text(\n    len(df)+0.5,\n    mean_val,\n    f\"Mean: {mean_val:.0f}\", \n    va=\"center\",\n    ha=\"left\",\n    color=\"red\",\n    backgroundcolor=\"white\"\n)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T09:31:05.336012Z","iopub.execute_input":"2025-07-23T09:31:05.336313Z","iopub.status.idle":"2025-07-23T09:31:06.677169Z","shell.execute_reply.started":"2025-07-23T09:31:05.336290Z","shell.execute_reply":"2025-07-23T09:31:06.676266Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_images(data_dir, classes, images_per_row=5):\n    num_classes = len(classes)\n    num_rows = (num_classes + images_per_row - 1) // images_per_row\n    \n    for i, cls in enumerate(classes):\n        ax = plt.subplot(num_rows, images_per_row, i + 1)\n        cls_dir = os.path.join(data_dir, cls)\n        image = random.choice(os.listdir(cls_dir))\n            \n        img_path = os.path.join(cls_dir, image)\n        img = Image.open(img_path)\n        \n        plt.imshow(img)\n        plt.axis('off')\n        plt.annotate(\n            cls,\n            xy=(0.05, 0.9),             \n            xycoords='axes fraction',    \n            fontsize=12,\n            fontweight='bold',\n            color='black',            \n            bbox=dict(facecolor='white', alpha=0.8, edgecolor='none')\n        )  \n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T09:31:09.329348Z","iopub.execute_input":"2025-07-23T09:31:09.329931Z","iopub.status.idle":"2025-07-23T09:31:09.336075Z","shell.execute_reply.started":"2025-07-23T09:31:09.329902Z","shell.execute_reply":"2025-07-23T09:31:09.335236Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"show_images(\n    data_dir=data_dir,\n    classes=class_names,\n    images_per_row=5\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T09:31:12.022465Z","iopub.execute_input":"2025-07-23T09:31:12.022806Z","iopub.status.idle":"2025-07-23T09:31:14.294640Z","shell.execute_reply.started":"2025-07-23T09:31:12.022755Z","shell.execute_reply":"2025-07-23T09:31:14.293832Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"widths, heights, channels = [], [], []\n\nfor cls in os.listdir(data_dir):\n    cls_path = os.path.join(data_dir, cls)\n    for img_name in os.listdir(cls_path)[:30]:\n        img = Image.open(os.path.join(cls_path, img_name))\n        w, h = img.size\n        widths.append(w)\n        heights.append(h)\n        channels.append(len(img.getbands()))\n\nfig, (ax1, ax2) = plt.subplots(2, 1)\n\nsns.histplot(widths, bins=20, ax=ax1, kde=True)\nax1.set_title(f\"Распределение ширины\")\n\nsns.histplot(heights, bins=20, ax=ax2, kde=True)\nax2.set_title(f\"Распределение высоты\")\n\nplt.show()\n\nprint(f\"Уникальные каналы: {set(channels)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T09:31:17.388055Z","iopub.execute_input":"2025-07-23T09:31:17.388382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_dataset(class_names, data_dir, train_dir, val_dir, test_dir, train_number=0.7, val_number=0.15, shuffle=False):\n    for dir_name in [train_dir, val_dir, test_dir]:\n        for class_name in class_names:\n            os.makedirs(os.path.join(dir_name, class_name), exist_ok=True)\n\n    for class_name in tqdm(class_names):\n        source_dir = os.path.join(data_dir, class_name)\n        class_files = os.listdir(source_dir)\n        \n        train_size = int(np.ceil(len(class_files) * train_number))\n        val_size = int(np.ceil(len(class_files) * val_number))\n        \n        if shuffle:\n            random.shuffle(class_files)\n\n        train_files = class_files[:train_size]\n        val_files = class_files[train_size:train_size + val_size]\n        test_files = class_files[train_size + val_size:]\n        \n        for sample, dir_path in [(train_files, train_dir), (val_files, val_dir), (test_files, test_dir)]:\n            for file in sample:\n                original_image_path = os.path.join(source_dir, file)\n                save_path = os.path.join(dir_path, class_name, file)\n                shutil.copy(original_image_path, save_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T20:18:11.492703Z","iopub.execute_input":"2025-07-22T20:18:11.493040Z","iopub.status.idle":"2025-07-22T20:18:11.500061Z","shell.execute_reply.started":"2025-07-22T20:18:11.493006Z","shell.execute_reply":"2025-07-22T20:18:11.499126Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dir = \"train\"\nval_dir = \"val\"\ntest_dir = \"test\"\n\nload_dataset(class_names, data_dir, train_dir, val_dir, test_dir, shuffle=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T20:18:14.182101Z","iopub.execute_input":"2025-07-22T20:18:14.182832Z","iopub.status.idle":"2025-07-22T20:18:47.463785Z","shell.execute_reply.started":"2025-07-22T20:18:14.182793Z","shell.execute_reply":"2025-07-22T20:18:47.462909Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mean = np.array([0.485, 0.456, 0.406])\nstd = np.array([0.229, 0.224, 0.225])\n\nnormalize = transforms.Normalize(\n    mean=mean,\n    std=std\n)\n\naffine = transforms.RandomAffine(\n    degrees=(-30, 30), \n    translate=(0.1, 0.1),\n)\n\nflip = transforms.RandomOrder([\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomVerticalFlip()\n])\n\nbrightness = transforms.ColorJitter(brightness=0.2)\n\ncontrast = transforms.ColorJitter(contrast=0.1)\n\nadd_gaussian_noise = transforms.Lambda(\n    lambda img: torch.randn_like(img)*0.1 + img\n)\n\nshift = transforms.RandomAffine(0, translate=(0.3, 0.3))\n\ngrayscale = transforms.RandomGrayscale(p=1)\n\nto_tensor =  transforms.ToTensor()\n\ndef add_random_shadows(img):\n    if not isinstance(img, torch.Tensor):\n        img = F.to_tensor(img)\n    \n    h, w = img.shape[1], img.shape[2]\n    shadow_mask = torch.ones((h, w))\n    \n    for _ in range(random.randint(1, 2)):\n        x_center = random.randint(0, w)\n        y_center = random.randint(0, h)\n        radius = random.randint(50, min(h, w) // 2)\n        \n        y, x = torch.meshgrid(torch.arange(h), torch.arange(w), indexing='ij')\n        dist = ((x - x_center) ** 2 + (y - y_center) ** 2).float().sqrt()\n        shadow = torch.clamp(1 - (dist / radius), 0, 1)\n        shadow_mask = torch.min(shadow_mask, shadow)\n    \n    img = img * shadow_mask.unsqueeze(0)\n    return F.to_pil_image(img)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T20:25:53.231782Z","iopub.execute_input":"2025-07-23T20:25:53.232430Z","iopub.status.idle":"2025-07-23T20:25:53.241380Z","shell.execute_reply.started":"2025-07-23T20:25:53.232403Z","shell.execute_reply":"2025-07-23T20:25:53.240505Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_transforms = [\n    # Аффинные преобразования + нормализация\n    transforms.Compose([\n        affine,\n        to_tensor,\n        normalize,\n    ]),\n    \n    # Только базовая нормализация\n    transforms.Compose([\n        to_tensor,\n        normalize,\n    ]),\n    \n    # Коррекция контраста + шум\n    transforms.Compose([\n        contrast,\n        to_tensor,\n        add_gaussian_noise,\n        normalize,\n    ]),\n\n    # Поворот + искажение\n    transforms.Compose([\n        transforms.RandomRotation(degrees=15),\n        transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),\n        to_tensor,\n        add_gaussian_noise,\n        normalize,\n    ]),\n    \n    # Отражения + шум\n    transforms.Compose([\n        flip,\n        to_tensor,\n        add_gaussian_noise,\n        normalize,\n    ]),\n    \n    # Сдвиг + шум\n    transforms.Compose([\n        shift,\n        to_tensor,\n        add_gaussian_noise,\n        normalize,\n    ]),\n\n    # Размытие\n    transforms.Compose([\n        transforms.RandomAdjustSharpness(sharpness_factor=2, p=0.5),  \n        add_random_shadows,\n        to_tensor,\n        normalize,\n    ]),\n    \n    # Яркость + контраст\n    transforms.Compose([\n        brightness,\n        contrast,\n        to_tensor,\n        normalize,\n    ]),\n]\n\nval_transforms = [\n    transforms.Compose([\n        to_tensor,\n        normalize,\n    ])\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T20:25:54.321038Z","iopub.execute_input":"2025-07-23T20:25:54.321624Z","iopub.status.idle":"2025-07-23T20:25:54.328328Z","shell.execute_reply.started":"2025-07-23T20:25:54.321596Z","shell.execute_reply":"2025-07-23T20:25:54.327465Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ImageFolderWithPaths(ImageFolder):\n    def __init__(self, root, transform=None):\n        classes = sorted_classes(root)\n        class_to_idx = {cls_name: i for i, cls_name in enumerate(classes)}\n        \n        super().__init__(root, transform=transform)\n        self.classes = classes\n        self.class_to_idx = class_to_idx\n        self.samples = self.make_dataset(\n            self.root, class_to_idx, extensions=self.extensions, is_valid_file=None\n        )\n        self.targets = [s[1] for s in self.samples]\n\n    def __getitem__(self, index):\n        path, target = self.samples[index]\n        sample = self.loader(path)\n        if self.transform is not None:\n            sample = self.transform(sample)\n        return sample, target, path\n\n\ntrain_dataset = ConcatDataset([\n    ImageFolderWithPaths(train_dir, train_transform)\n    for train_transform in train_transforms\n])\n\nval_dataset = ConcatDataset([\n    ImageFolderWithPaths(val_dir, val_transform)\n    for val_transform in val_transforms\n])\n\ntest_dataset = ConcatDataset([\n    ImageFolderWithPaths(test_dir,transform)\n    for transform in val_transforms\n])\n\ntrain_dataloader = DataLoader(\n    train_dataset, batch_size=batch_size, shuffle=True, num_workers=4, drop_last=True, pin_memory=True\n)\n\nval_dataloader = DataLoader(\n    val_dataset, batch_size=batch_size, shuffle=True, num_workers=4, drop_last=True, pin_memory=True\n)\n\ntest_dataloader = DataLoader(\n    test_dataset, batch_size=1, shuffle=True, num_workers=4, pin_memory=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T20:18:55.130335Z","iopub.execute_input":"2025-07-22T20:18:55.130669Z","iopub.status.idle":"2025-07-22T20:18:55.328968Z","shell.execute_reply.started":"2025-07-22T20:18:55.130642Z","shell.execute_reply":"2025-07-22T20:18:55.328081Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_batch_with_paths(dataloader, class_names, n_images=4):\n    batch = next(iter(dataloader))\n    images, labels, paths = batch[:3]\n    \n    for i in range(min(n_images, len(images))):\n        plt.subplot(1, n_images, i+1)\n        \n        img = images[i].permute(1, 2, 0).numpy()\n        img = img * std + mean\n        img = np.clip(img, 0, 1)\n        \n        true_class = os.path.basename(os.path.dirname(paths[i]))\n        \n        plt.imshow(img)\n        plt.title(f\"True: {true_class}\\nLabel: {class_names[labels[i]]}\\n{os.path.basename(paths[i])}\")\n        plt.axis('off')\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T20:18:57.695600Z","iopub.execute_input":"2025-07-22T20:18:57.696439Z","iopub.status.idle":"2025-07-22T20:18:57.704084Z","shell.execute_reply.started":"2025-07-22T20:18:57.696401Z","shell.execute_reply":"2025-07-22T20:18:57.702991Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"show_batch_with_paths(train_dataloader, class_names)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T20:18:59.641903Z","iopub.execute_input":"2025-07-22T20:18:59.642661Z","iopub.status.idle":"2025-07-22T20:19:01.576206Z","shell.execute_reply.started":"2025-07-22T20:18:59.642632Z","shell.execute_reply":"2025-07-22T20:19:01.575003Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Train size : {len(train_dataloader)} \\nVal size : {len(val_dataloader)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T20:19:02.019440Z","iopub.execute_input":"2025-07-22T20:19:02.019780Z","iopub.status.idle":"2025-07-22T20:19:02.025195Z","shell.execute_reply.started":"2025-07-22T20:19:02.019751Z","shell.execute_reply":"2025-07-22T20:19:02.024308Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_batch, y_batch, _ = next(iter(train_dataloader))\nplt.imshow(X_batch[0].permute(1, 2, 0).numpy() * std + mean);","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T20:19:04.974911Z","iopub.execute_input":"2025-07-22T20:19:04.975483Z","iopub.status.idle":"2025-07-22T20:19:06.341311Z","shell.execute_reply.started":"2025-07-22T20:19:04.975452Z","shell.execute_reply":"2025-07-22T20:19:06.340401Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BasicBlock(nn.Module):\n    def __init__(self, inp, out, downsample=False):\n        super().__init__()\n        stride = 2 if downsample else 1\n        self.conv1 = nn.Conv2d(inp, out, 3, stride=stride, padding=1, bias=False)\n        self.conv2 = nn.Conv2d(out, out, 3, padding=1, bias=False)\n        self.shortcut = nn.Sequential()\n        \n        if downsample or inp != out:\n            self.shortcut = nn.Sequential(\n                nn.Conv2d(inp, out, 1, stride=stride, bias=False),\n                nn.BatchNorm2d(out)\n            )\n        \n        self.bn1 = nn.BatchNorm2d(out)\n        self.bn2 = nn.BatchNorm2d(out)\n        self.relu = nn.ReLU()\n\n    def forward(self, x):\n        identity = self.shortcut(x)\n        out = self.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        out += identity\n        return self.relu(out)\n\nclass Bottleneck(nn.Module):\n    def __init__(self, inp, out, downsample=False):\n        super().__init__()\n        stride = 2 if downsample else 1\n        self.conv1 = nn.Conv2d(inp, out//4, 1, bias=False)\n        self.conv2 = nn.Conv2d(out//4, out//4, 3, stride=stride, padding=1, bias=False)\n        self.conv3 = nn.Conv2d(out//4, out, 1, bias=False)\n        self.shortcut = nn.Sequential()\n        \n        if downsample or inp != out:\n            self.shortcut = nn.Sequential(\n                nn.Conv2d(inp, out, 1, stride=stride, bias=False),\n                nn.BatchNorm2d(out)\n            )\n        \n        self.bn1 = nn.BatchNorm2d(out//4)\n        self.bn2 = nn.BatchNorm2d(out//4)\n        self.bn3 = nn.BatchNorm2d(out)\n        self.relu = nn.ReLU()\n\n    def forward(self, x):\n        identity = self.shortcut(x)\n        out = self.relu(self.bn1(self.conv1(x)))\n        out = self.relu(self.bn2(self.conv2(out)))\n        out = self.bn3(self.conv3(out))\n        out += identity\n        return self.relu(out)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T20:26:03.535341Z","iopub.execute_input":"2025-07-23T20:26:03.535653Z","iopub.status.idle":"2025-07-23T20:26:03.545974Z","shell.execute_reply.started":"2025-07-23T20:26:03.535630Z","shell.execute_reply":"2025-07-23T20:26:03.545112Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class NN(nn.Module):\n    def __init__(self, outputs_number=50):\n        super().__init__()\n        \n        self.stem = nn.Sequential(\n            nn.Conv2d(3, 32, 3, stride=1, padding=1, bias=False),  # (batch_size, 32, 224, 224)\n            nn.BatchNorm2d(32),\n            nn.ReLU(),\n            nn.MaxPool2d(3, stride=2, padding=1)  # (batch_size, 32, 112, 112)\n        )\n        \n        self.layers = nn.Sequential(\n            BasicBlock(32, 64, downsample=True),   # (batch_size, 64, 56, 56)\n            BasicBlock(64, 64),                    # (batch_size, 64, 56, 56)\n            \n            Bottleneck(64, 128, downsample=True),  # (batch_size, 128, 28, 28) \n            Bottleneck(128, 128),                  # )batch_size, 128, 28, 28)\n            \n            BasicBlock(128, 256, downsample=True), # (batch_size, 256, 14, 14)\n            BasicBlock(256, 256),                  # (batch_size, 256, 14, 14)\n            \n            Bottleneck(256, 512, downsample=True), # (batch_size, 512, 7, 7)\n            Bottleneck(512, 512)                   # (batch_size, 512, 7, 7)\n        )\n        \n        self.avgpool = nn.AdaptiveAvgPool2d(1)     # (batch_size, 512, 1, 1)\n        self.fc = nn.Linear(512, outputs_number)      # (batch_size, 50)\n\n    def forward(self, x):\n        x = self.stem(x)\n        x = self.layers(x)\n        x = self.avgpool(x)\n        x = x.view(x.size(0), -1)\n        return self.fc(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T20:26:05.096807Z","iopub.execute_input":"2025-07-23T20:26:05.097677Z","iopub.status.idle":"2025-07-23T20:26:05.104349Z","shell.execute_reply.started":"2025-07-23T20:26:05.097648Z","shell.execute_reply":"2025-07-23T20:26:05.103372Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class WRN(torch.nn.Module):\n    def __init__(self, outputs_number):\n        super(WRN, self).__init__()\n        self.net = torch.hub.load('pytorch/vision:v0.10.0', 'wide_resnet50_2', pretrained=True)\n        \n        for param in self.net.parameters():\n            param.requires_grad = False\n        \n        for param in self.net.layer4.parameters():\n            param.requires_grad = True\n\n        TransferModelOutputs = self.net.fc.in_features\n        self.net.fc = torch.nn.Sequential(\n            torch.nn.Linear(TransferModelOutputs, 256, bias=False),\n            torch.nn.BatchNorm1d(256), \n            torch.nn.ReLU(),  \n            torch.nn.Dropout(0.5),  \n            torch.nn.Linear(256, outputs_number) \n        )\n\n    def forward(self, x):\n        return self.net(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T20:26:09.356598Z","iopub.execute_input":"2025-07-23T20:26:09.356906Z","iopub.status.idle":"2025-07-23T20:26:09.362949Z","shell.execute_reply.started":"2025-07-23T20:26:09.356881Z","shell.execute_reply":"2025-07-23T20:26:09.361985Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = WRN(50)\n# model = NN(50)\nmodel = model.to(device)\n\nif gpu_count > 1:\n    model = nn.DataParallel(model)\n\nloss = torch.nn.CrossEntropyLoss(label_smoothing=0.1)\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-3, amsgrad=True)\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T14:00:15.828450Z","iopub.execute_input":"2025-07-22T14:00:15.828691Z","iopub.status.idle":"2025-07-22T14:00:15.857108Z","shell.execute_reply.started":"2025-07-22T14:00:15.828663Z","shell.execute_reply":"2025-07-22T14:00:15.856555Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_model(model, train_dataloader, val_dataloader, loss, optimizer, scheduler, num_epochs, save_path='best_model.pth'):\n    best_val_loss = float('inf')\n    best_val_acc = 0\n    best_model_weights = None\n    \n    train_loss = torch.zeros(num_epochs)\n    val_loss = torch.zeros(num_epochs)\n    train_acc = torch.zeros(num_epochs)\n    val_acc = torch.zeros(num_epochs)\n\n    for epoch in range(num_epochs):\n        print(f'Epoch {epoch}/{num_epochs - 1}:')\n\n        for phase in ['train', 'val']:\n            dataloader = train_dataloader if phase == 'train' else val_dataloader\n            model.train() if phase == 'train' else model.eval()\n\n            running_loss = 0.0\n            running_corrects = 0\n            total = 0\n\n            for inputs, labels, _ in tqdm(dataloader, desc=phase, leave=False):\n                inputs = inputs.to(device)\n                labels = labels.to(device)\n                optimizer.zero_grad()\n\n                with torch.set_grad_enabled(phase == 'train'):\n                    preds = model(inputs)\n                    preds_class = preds.argmax(dim=1)\n\n                    loss_value = loss(preds, labels)\n                    if phase == 'train':\n                        loss_value.backward()\n                        optimizer.step()\n\n                running_loss += loss_value.item() * inputs.size(0)\n                running_corrects += (preds_class == labels).sum().item()\n                total += labels.size(0)\n\n            epoch_loss = running_loss / total\n            epoch_acc = running_corrects / total\n\n            if phase == 'train':\n                train_loss[epoch] = epoch_loss\n                train_acc[epoch] = epoch_acc\n            else:\n                val_loss[epoch] = epoch_loss\n                val_acc[epoch] = epoch_acc\n            \n                scheduler.step(epoch_loss)\n\n                if best_val_acc < epoch_acc:\n                    best_val_loss = epoch_loss\n                    best_val_acc = epoch_acc\n                    if isinstance(model, nn.DataParallel):\n                        best_model_weights = model.module.state_dict()\n                    else:\n                        best_model_weights = model.state_dict()\n\n            print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')\n\n    if best_model_weights is not None:\n        torch.save(best_model_weights, save_path)\n        print(f'Best model saved to {save_path} with loss: {best_val_loss:.4f}, acc: {best_val_acc:.4f}')\n\n    return train_loss, val_loss, train_acc, val_acc\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T14:00:15.857943Z","iopub.execute_input":"2025-07-22T14:00:15.858148Z","iopub.status.idle":"2025-07-22T14:00:15.867652Z","shell.execute_reply.started":"2025-07-22T14:00:15.858118Z","shell.execute_reply":"2025-07-22T14:00:15.866748Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loss, val_loss, train_acc, val_acc = train_model(\n    model, train_dataloader, val_dataloader, loss, optimizer, scheduler, num_epochs=1, save_path=\"wrn_35.pth\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T14:00:15.868827Z","iopub.execute_input":"2025-07-22T14:00:15.869526Z","execution_failed":"2025-07-22T14:01:08.801Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_training_curves(train_loss, val_loss, train_acc, val_acc, save_path='training_curves.png'):\n    fig, axes = plt.subplots(1, 2, figsize=(12, 5))\n\n    axes[0].plot(train_loss, label='Train Loss', linewidth=2)\n    axes[0].plot(val_loss, label='Val Loss', linewidth=2)\n    axes[0].set_title('Loss per Epoch', fontsize=14)\n    axes[0].set_xlabel('Epoch', fontsize=12)\n    axes[0].set_ylabel('Loss', fontsize=12)\n    axes[0].legend(loc='upper right', fontsize=10)\n\n    axes[1].plot(train_acc, label='Train Accuracy', linewidth=2)\n    axes[1].plot(val_acc, label='Val Accuracy', linewidth=2)\n    axes[1].set_title('Accuracy per Epoch', fontsize=14)\n    axes[1].set_xlabel('Epoch', fontsize=12)\n    axes[1].set_ylabel('Accuracy', fontsize=12)\n    axes[1].legend(loc='lower right', fontsize=10)\n\n    plt.savefig(save_path, dpi=150)\n    plt.show()","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-22T14:01:08.802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_training_curves(train_loss.numpy(), val_loss.numpy(), train_acc.numpy(), val_acc.numpy())","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-22T14:01:08.802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def evaluate_model(model, dataloader):\n    model.eval()\n    \n    all_preds = []\n    all_labels = []\n\n    with torch.no_grad():\n        for inputs, labels, _ in tqdm(dataloader):\n            inputs = inputs.to(device)\n            labels = labels.to(device)\n\n            outputs = model(inputs)\n            preds = outputs.argmax(dim=1)\n\n            all_preds.extend(preds.cpu().numpy())\n            all_labels.extend(labels.cpu().numpy())\n\n    accuracy = accuracy_score(all_labels, all_preds)\n    precision = precision_score(all_labels, all_preds, average='micro')\n    recall = recall_score(all_labels, all_preds, average='micro')\n    f1 = f1_score(all_labels, all_preds, average='micro')\n\n    print(f'Accuracy : {accuracy:.4f}')\n    print(f'Precision: {precision:.4f}')\n    print(f'Recall   : {recall:.4f}')\n    print(f'F1-score : {f1:.4f}')\n\n    return all_labels, all_preds\n\ndef plot_confusion_matrix(labels, preds, class_names, save_path='confmatrix.png'):\n    cm = confusion_matrix(labels, preds)\n    fig, ax = plt.subplots(figsize=(16, 14))\n    \n    disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=class_names)\n    disp.plot(ax=ax, cmap=plt.cm.Blues, colorbar=True)\n\n    plt.title('Confusion Matrix', fontsize=18)\n    plt.xlabel('Predicted', fontsize=14)\n    plt.ylabel('True', fontsize=14)\n\n    plt.xticks(rotation=90, fontsize=10)\n    plt.yticks(fontsize=10)\n\n    plt.savefig(save_path, dpi=150)\n    plt.show()\n\n    return cm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T21:19:06.249272Z","iopub.execute_input":"2025-07-22T21:19:06.250230Z","iopub.status.idle":"2025-07-22T21:19:06.259270Z","shell.execute_reply.started":"2025-07-22T21:19:06.250196Z","shell.execute_reply":"2025-07-22T21:19:06.258283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_labels, all_preds = evaluate_model(model, val_dataloader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T21:18:54.073165Z","iopub.execute_input":"2025-07-22T21:18:54.073841Z","iopub.status.idle":"2025-07-22T21:19:02.088189Z","shell.execute_reply.started":"2025-07-22T21:18:54.073812Z","shell.execute_reply":"2025-07-22T21:19:02.087187Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_roc_curves(model, dataloader, class_names):\n    model.eval()\n    \n    all_labels = []\n    all_probs = []\n\n    with torch.no_grad():\n        for inputs, labels, _ in tqdm(dataloader):\n            inputs = inputs.to(device)\n            outputs = model(inputs)\n            probs = torch.softmax(outputs, dim=1)\n            \n            all_probs.append(probs.cpu().numpy())\n            all_labels.append(labels.numpy())\n\n    all_probs = np.concatenate(all_probs)\n    all_labels = np.concatenate(all_labels)\n    \n    y_true = label_binarize(all_labels, classes=range(len(class_names)))\n    n_classes = y_true.shape[1]\n\n    plt.figure(figsize=(12, 10))\n    \n    for i in range(n_classes):\n        fpr, tpr, _ = roc_curve(y_true[:, i], all_probs[:, i])\n        roc_auc = auc(fpr, tpr)\n        plt.plot(fpr, tpr, lw=2, label=f'Class {class_names[i]} (AUC = {roc_auc:.2f})')\n\n    plt.plot([0, 1], [0, 1], 'k--', lw=2)\n    plt.xlim([0.0, 1.0])\n    plt.ylim([0.0, 1.05])\n    plt.xlabel('FPR')\n    plt.ylabel('TPR')\n    plt.title('ROC Curves')\n    plt.legend(loc='lower right', fontsize='small', ncol=2)\n    plt.savefig('roc_curves.png', dpi=150)\n    plt.show()","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-22T14:01:08.802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_roc_curves(model, val_dataloader, class_names)","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-22T14:01:08.802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cm = plot_confusion_matrix(all_labels, all_preds, class_names)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T21:23:30.826521Z","iopub.execute_input":"2025-07-22T21:23:30.827223Z","iopub.status.idle":"2025-07-22T21:23:40.871661Z","shell.execute_reply.started":"2025-07-22T21:23:30.827194Z","shell.execute_reply":"2025-07-22T21:23:40.870842Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def confused_classes(cm, top_n=6):\n    cm_no_diag = cm.copy()\n    np.fill_diagonal(cm_no_diag, 0)\n    confused_pairs = []\n\n    for _ in range(top_n):\n        idx = np.unravel_index(np.argmax(cm_no_diag), cm_no_diag.shape)\n        confused_pairs.append(idx)\n        cm_no_diag[idx] = 0\n        cm_no_diag[idx[1], idx[0]] = 0 \n    \n    return sorted(set(i for pair in confused_pairs for i in pair))\n\ndef filter_dataset_by_classes(dataset, target_classes):\n    indices = [i for i, (_, label, _) in enumerate(dataset)\n               if label in target_classes]\n    return Subset(dataset, indices)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T21:23:45.056591Z","iopub.execute_input":"2025-07-22T21:23:45.057224Z","iopub.status.idle":"2025-07-22T21:23:45.062400Z","shell.execute_reply.started":"2025-07-22T21:23:45.057195Z","shell.execute_reply":"2025-07-22T21:23:45.061608Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def finetune(model, val_dataset, confused_classes, num_epochs=1, lr=1e-5):\n    subset = filter_dataset_by_classes(val_dataset, confused_classes)\n    dataloader = DataLoader(subset, batch_size=batch_size, shuffle=True, num_workers=4, pin_memory=True)\n\n    model.train()\n    optimizer = torch.optim.Adam(model.parameters(), lr=lr)\n    loss_fn = torch.nn.CrossEntropyLoss()\n\n    for epoch in range(num_epochs):\n        running_loss = 0.0\n        correct = 0\n        total = 0\n        for inputs, labels, _ in tqdm(dataloader):\n            inputs, labels = inputs.to(device), labels.to(device)\n            optimizer.zero_grad()\n            outputs = model(inputs)\n            loss = loss_fn(outputs, labels)\n            loss.backward()\n            optimizer.step()\n\n            running_loss += loss.item() * inputs.size(0)\n            correct += (outputs.argmax(1) == labels).sum().item()\n            total += labels.size(0)\n        \n        print(f\"Epoch {epoch} Loss: {running_loss/total:.4f}, Acc: {correct/total:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T09:52:35.242400Z","iopub.execute_input":"2025-07-23T09:52:35.243082Z","iopub.status.idle":"2025-07-23T09:52:35.249624Z","shell.execute_reply.started":"2025-07-23T09:52:35.243053Z","shell.execute_reply":"2025-07-23T09:52:35.248828Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"top_confused_classes = confused_classes(cm, top_n=6)\nfinetune(model, val_dataset, top_confused_classes, num_epochs=6)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T21:25:37.954257Z","iopub.execute_input":"2025-07-22T21:25:37.954558Z","iopub.status.idle":"2025-07-22T21:25:43.285679Z","shell.execute_reply.started":"2025-07-22T21:25:37.954537Z","shell.execute_reply":"2025-07-22T21:25:43.284505Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_labels, all_preds = evaluate_model(model, test_dataloader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-22T21:27:28.468305Z","iopub.execute_input":"2025-07-22T21:27:28.469065Z","iopub.status.idle":"2025-07-22T21:27:34.429089Z","shell.execute_reply.started":"2025-07-22T21:27:28.469029Z","shell.execute_reply":"2025-07-22T21:27:34.428014Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def inference(model, folder_path, output_file='predictions.csv'):\n    model.eval()\n    image_files = [f for f in os.listdir(folder_path)]\n    image_files.sort()\n    results = []\n    \n    with torch.no_grad():\n        for filename in tqdm(image_files):\n            img_path = os.path.join(folder_path, filename)\n            img = Image.open(img_path)\n            img_tensor = val_transforms[0](img).unsqueeze(0).to(device)\n            \n            preds = model(img_tensor)\n            probs = torch.softmax(preds, dim=1)\n            pred_class = probs.argmax(dim=1).item()\n            \n            index = os.path.splitext(filename)[0]\n            results.append({'index': index, 'label': pred_class})\n    \n    df = pd.DataFrame(results)\n    df.to_csv(output_file, index=False, header=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T20:26:18.729459Z","iopub.execute_input":"2025-07-23T20:26:18.730196Z","iopub.status.idle":"2025-07-23T20:26:18.736097Z","shell.execute_reply.started":"2025-07-23T20:26:18.730166Z","shell.execute_reply":"2025-07-23T20:26:18.735122Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# inference(model, \"/kaggle/input/butterflies-classification/test_butterflies/valid\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-07-22T14:01:08.802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class EnsembleModel(nn.Module):\n    def __init__(self, model1, model2):\n        super(EnsembleModel, self).__init__()\n        self.model1 = model1\n        self.model2 = model2\n        \n    def forward(self, x):\n        logits1 = self.model1(x)\n        logits2 = self.model2(x)\n        \n        probs1 = Func.softmax(logits1, dim=1)\n        probs2 = Func.softmax(logits2, dim=1)\n        \n        avg_probs = (probs1 + probs2) / 2\n        \n        return avg_probs","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T20:26:33.378108Z","iopub.execute_input":"2025-07-23T20:26:33.378932Z","iopub.status.idle":"2025-07-23T20:26:33.384226Z","shell.execute_reply.started":"2025-07-23T20:26:33.378901Z","shell.execute_reply":"2025-07-23T20:26:33.383069Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model1 = NN(50)\nmodel2 = WRN(50)\n\nmodel2.load_state_dict(torch.load('/kaggle/working/wrn_35.pth'))\nmodel1.load_state_dict(torch.load('/kaggle/input/models/best_model.pth'))\n\nensemble_model = EnsembleModel(model1, model2).to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T20:26:53.420107Z","iopub.execute_input":"2025-07-23T20:26:53.420555Z","iopub.status.idle":"2025-07-23T20:27:00.211467Z","shell.execute_reply.started":"2025-07-23T20:26:53.420520Z","shell.execute_reply":"2025-07-23T20:27:00.210680Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# inference(ensemble_model, \"/kaggle/input/butterflies-classification/test_butterflies/valid\", output_file='ensemble_predictions.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-23T20:27:01.028586Z","iopub.execute_input":"2025-07-23T20:27:01.029417Z","iopub.status.idle":"2025-07-23T20:27:07.524649Z","shell.execute_reply.started":"2025-07-23T20:27:01.029384Z","shell.execute_reply":"2025-07-23T20:27:07.523796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def clear_gpu_memory():\n    gc.collect()\n    if torch.cuda.is_available():\n        torch.cuda.empty_cache()\n        torch.cuda.ipc_collect()\nclear_gpu_memory()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}