{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nimport os\nimport cv2\nfrom torchvision.transforms import v2\nfrom torch import nn\nfrom sklearn.metrics import precision_score, recall_score, accuracy_score, f1_score, classification_report\nimport random\nfrom torch.optim.lr_scheduler import LinearLR, CosineAnnealingLR, SequentialLR","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-12T13:16:27.970309Z","iopub.execute_input":"2026-09-12T13:16:27.970591Z","iopub.status.idle":"2026-09-12T13:16:38.445894Z","shell.execute_reply.started":"2026-09-12T13:16:27.970566Z","shell.execute_reply":"2026-09-12T13:16:38.445285Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.accelerator.current_accelerator() if torch.accelerator.is_available() else 'cpu'\ndevice","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T13:16:38.44735Z","iopub.execute_input":"2026-09-12T13:16:38.447855Z","iopub.status.idle":"2026-09-12T13:16:38.72802Z","shell.execute_reply.started":"2026-09-12T13:16:38.447815Z","shell.execute_reply":"2026-09-12T13:16:38.727093Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Датасет","metadata":{}},{"cell_type":"code","source":"class CassavaLeafDataset(Dataset):\n    def __init__(self, img_dir, label_dir, is_train=True, train_size=0.8, transform=None):\n        self.img_dir = img_dir\n        self.img_filenames = sorted(os.listdir(img_dir))\n        n = len(self.img_filenames)\n            \n        random.shuffle(self.img_filenames)\n        if is_train:\n            self.img_filenames = self.img_filenames[:int(train_size * n)]\n        else:\n            self.img_filenames = self.img_filenames[int(train_size * n):]\n            \n        self.label_dir = label_dir\n        self.labels = pd.read_csv(label_dir)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.img_filenames)\n\n    def __getitem__(self, idx):\n        cur_img = self.img_filenames[idx]\n        img = cv2.imread(os.path.join(self.img_dir, cur_img), cv2.IMREAD_COLOR_RGB)\n        if self.transform is not None:\n            img = self.transform(img)\n        \n        label = self.labels[self.labels['image_id'] == cur_img]['label'].iloc[0]\n        return img, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T13:16:38.729103Z","iopub.execute_input":"2026-09-12T13:16:38.729453Z","iopub.status.idle":"2026-09-12T13:16:38.743211Z","shell.execute_reply.started":"2026-09-12T13:16:38.729418Z","shell.execute_reply":"2026-09-12T13:16:38.742294Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mean = [0.485, 0.456, 0.406]\nstd = [0.229, 0.224, 0.225]\n\ntransform = v2.Compose([\n    v2.ToImage(),\n    v2.Resize((224, 224)),\n    v2.ToDtype(torch.float32, scale=True),\n    v2.Normalize(mean, std)\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T13:16:38.74432Z","iopub.execute_input":"2026-09-12T13:16:38.744844Z","iopub.status.idle":"2026-09-12T13:16:38.75693Z","shell.execute_reply.started":"2026-09-12T13:16:38.744821Z","shell.execute_reply":"2026-09-12T13:16:38.756336Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Модель","metadata":{}},{"cell_type":"code","source":"class SimpleModel(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.conv_stack = nn.Sequential(\n            nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1, bias=False),\n            nn.BatchNorm2d(32),\n            nn.SiLU(inplace=True),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n            \n            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=1, bias=False),\n            nn.BatchNorm2d(32),\n            nn.SiLU(inplace=True),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n            \n            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1),\n            nn.BatchNorm2d(64),\n            nn.SiLU(inplace=True),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n            \n            nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=1),\n            nn.BatchNorm2d(64),\n            nn.SiLU(inplace=True),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n        )\n        self.flatten = nn.Flatten()\n        self.linear_relu_stack = nn.Sequential(\n            nn.Linear(14 * 14 * 64, 128),\n            nn.SiLU(inplace=True),\n            nn.Dropout(p=0.3),\n            nn.Linear(128, 5)\n        )\n\n    def forward(self, x):\n        x = self.conv_stack(x)\n        x = self.flatten(x)\n        logits = self.linear_relu_stack(x)\n        return logits","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T13:16:38.757886Z","iopub.execute_input":"2026-09-12T13:16:38.758247Z","iopub.status.idle":"2026-09-12T13:16:38.770557Z","shell.execute_reply.started":"2026-09-12T13:16:38.758223Z","shell.execute_reply":"2026-09-12T13:16:38.769765Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_loop(model, loss_fn, optimizer, dataloader, logging_steps, train_losses=None):\n    model.train()\n    batch_size = dataloader.batch_size\n    num_samples = len(dataloader.dataset)\n    total_loss = 0\n\n    for i, (X, y) in enumerate(dataloader):\n        X, y = X.to(device), y.to(device)\n\n        pred = model(X)\n        loss = loss_fn(pred, y)\n        loss.backward()\n\n        total_loss += loss.item() * X.size(0)\n        \n        optimizer.step()\n        optimizer.zero_grad()\n\n\n        if i % logging_steps == 0 or i == len(dataloader) - 1:\n            print(f\"{(i + 1) * batch_size:<6}/ {num_samples:<6} | loss: {loss.item():.5f}\")\n\n    epoch_loss = total_loss / num_samples\n    if train_losses:\n        train_losses.append(epoch_loss)\n\n    return epoch_loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T13:16:38.771419Z","iopub.execute_input":"2026-09-12T13:16:38.771607Z","iopub.status.idle":"2026-09-12T13:16:38.787993Z","shell.execute_reply.started":"2026-09-12T13:16:38.771587Z","shell.execute_reply":"2026-09-12T13:16:38.787171Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def test_loop(model, loss_fn, dataloader, test_losses=None):\n    model.eval()\n\n    total_loss = 0\n    num_samples = len(dataloader.dataset)\n    all_preds = []\n    all_true = []\n    \n    for X, y in dataloader:\n        X, y = X.to(device), y.to(device)\n\n        with torch.no_grad():\n            pred = model(X)\n            loss = loss_fn(pred, y)\n            labels = pred.argmax(1)\n            all_preds.append(labels)\n            all_true.append(y)\n            total_loss += loss.item() * X.size(0)\n\n    \n    epoch_loss = total_loss / num_samples\n    if test_losses is not None:\n        test_losses.append(epoch_loss)\n\n    all_true = torch.cat(all_true).tolist()\n    all_preds = torch.cat(all_preds).tolist()\n    \n    print(classification_report(all_true, all_preds, zero_division=0))\n    print(f'Test loss: {epoch_loss}')\n    return epoch_loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T13:16:38.789993Z","iopub.execute_input":"2026-09-12T13:16:38.790354Z","iopub.status.idle":"2026-09-12T13:16:38.805186Z","shell.execute_reply.started":"2026-09-12T13:16:38.79033Z","shell.execute_reply":"2026-09-12T13:16:38.804555Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"imgs_dir = '/kaggle/input/competitions/cassava-leaf-disease-classification/train_images'\nlabels_dir = '/kaggle/input/competitions/cassava-leaf-disease-classification/train.csv'\n\nbatch_size = 64\ntrain_dataset = CassavaLeafDataset(imgs_dir, labels_dir, is_train=True, train_size=0.8, transform=transform)\ntest_dataset = CassavaLeafDataset(imgs_dir, labels_dir, is_train=False, train_size=0.8, transform=transform)\n\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size)\ntest_loader = DataLoader(test_dataset, batch_size=batch_size)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T13:30:03.957113Z","iopub.execute_input":"2026-09-12T13:30:03.957529Z","iopub.status.idle":"2026-09-12T13:30:04.023666Z","shell.execute_reply.started":"2026-09-12T13:30:03.9575Z","shell.execute_reply":"2026-09-12T13:30:04.02275Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"weights_path = '/kaggle/input/models/slnkvdns/simple-cassava-leaf/pytorch/default/1/simple_model_weights.pth'\nmodel = SimpleModel()\nmodel.load_state_dict(torch.load(weights_path, map_location=device))\nmodel.to(device);","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T13:30:07.371526Z","iopub.execute_input":"2026-09-12T13:30:07.37198Z","iopub.status.idle":"2026-09-12T13:30:07.41429Z","shell.execute_reply.started":"2026-09-12T13:30:07.371948Z","shell.execute_reply":"2026-09-12T13:30:07.413661Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Обучение простой модели","metadata":{}},{"cell_type":"code","source":"lr = 1e-3\ntotal_epochs = 15\nwarmup_steps = 5\noptimizer = torch.optim.AdamW(model.parameters(), lr=lr)\n\nlinear_lr = LinearLR(optimizer, 1e-2, 1, total_iters=warmup_steps)\ncosine_lr = CosineAnnealingLR(optimizer, T_max=total_epochs - warmup_steps)\nscheduler = SequentialLR(optimizer, schedulers=[linear_lr, cosine_lr], milestones=[warmup_steps])\nloss_fn = nn.CrossEntropyLoss()\n\nlogging_steps = 10\ntrain_losses = []\ntest_losses = []","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-11T11:18:45.43121Z","iopub.execute_input":"2026-09-11T11:18:45.431609Z","iopub.status.idle":"2026-09-11T11:18:45.437653Z","shell.execute_reply.started":"2026-09-11T11:18:45.43158Z","shell.execute_reply":"2026-09-11T11:18:45.437037Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_epochs = 10\nfor i in range(total_epochs):\n    print(f\"====Epoch {i + 1}====\")\n    train_loop(model, loss_fn, optimizer, train_loader, logging_steps, train_losses)\n    print('\\nTest metrics:')\n    test_loop(model, loss_fn, test_loader, test_losses)\n    scheduler.step()\n    print()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-11T11:18:51.247651Z","iopub.execute_input":"2026-09-11T11:18:51.24814Z","iopub.status.idle":"2026-09-11T12:24:04.111791Z","shell.execute_reply.started":"2026-09-11T11:18:51.248109Z","shell.execute_reply":"2026-09-11T12:24:04.111032Z"},"scrolled":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_submission(path, model):\n    model.eval()\n    dct = {}\n    img_ids = os.listdir(path)\n    \n    for img_id in img_ids:\n        img = cv2.imread(os.path.join(path, img_id), cv2.IMREAD_COLOR_RGB)\n        transformed_img = transform(img).unsqueeze(0).to(device)\n        prediction = model(transformed_img)[0]\n        label = prediction.argmax(0).item()\n        dct[img_id] = label\n    df = pd.DataFrame(list(dct.items()), columns=['image_id', 'label'])\n    df.to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-11T13:33:09.379103Z","iopub.execute_input":"2026-09-11T13:33:09.379675Z","iopub.status.idle":"2026-09-11T13:33:09.384405Z","shell.execute_reply.started":"2026-09-11T13:33:09.379648Z","shell.execute_reply":"2026-09-11T13:33:09.383676Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f'Число параметров простой модели: {sum(x.numel() for x in model.parameters())}')\nprint(f'Обучаемые параметры: {sum(x.numel() for x in model.parameters() if x.requires_grad)}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T13:38:57.339685Z","iopub.execute_input":"2026-09-12T13:38:57.340192Z","iopub.status.idle":"2026-09-12T13:38:57.345319Z","shell.execute_reply.started":"2026-09-12T13:38:57.340164Z","shell.execute_reply":"2026-09-12T13:38:57.344493Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DepthwiseConv(nn.Module):\n    def __init__(self, in_channels, out_channels, kernel_size=3):\n        super().__init__()\n        padding = kernel_size // 2\n        self.conv1 = nn.Conv2d(in_channels, in_channels, kernel_size, groups=in_channels, padding=padding, bias=False)\n        self.bn1 = nn.BatchNorm2d(in_channels)\n        self.relu = nn.ReLU()\n        self.conv2 = nn.Conv2d(in_channels, out_channels, kernel_size=1)\n        self.bn2 = nn.BatchNorm2d(out_channels)\n\n    def forward(self, x):\n        x = self.relu(self.bn1(self.conv1(x)))\n        x = self.relu(self.bn2(self.conv2(x)))\n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T14:16:26.10387Z","iopub.execute_input":"2026-09-12T14:16:26.104718Z","iopub.status.idle":"2026-09-12T14:16:26.109935Z","shell.execute_reply.started":"2026-09-12T14:16:26.104689Z","shell.execute_reply":"2026-09-12T14:16:26.109224Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class LightModel(nn.Module):\n    def __init__(self, num_classes):\n        super().__init__()\n        self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1)\n        self.bn1 = nn.BatchNorm2d(32)\n        self.silu = nn.SiLU()\n        self.max_pool1 = nn.MaxPool2d(kernel_size=2, stride=2)\n        self.depthwise_stack = nn.Sequential(\n            DepthwiseConv(in_channels=32, out_channels=64),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n            DepthwiseConv(in_channels=64, out_channels=128),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n            DepthwiseConv(in_channels=128, out_channels=256),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n            DepthwiseConv(in_channels=256, out_channels=512),\n            nn.MaxPool2d(kernel_size=2, stride=2)\n        )\n        self.gap = nn.AdaptiveAvgPool2d(1)\n        self.flatten = nn.Flatten()\n        self.linear_relu_stack = nn.Sequential(\n            nn.Linear(512, 512),\n            nn.SiLU(),\n            nn.Dropout(p=0.3),\n            nn.Linear(512, num_classes)\n        )\n\n    def forward(self, x):\n        x = self.silu(self.bn1(self.conv1(x)))\n        x = self.max_pool1(x)\n        x = self.depthwise_stack(x)\n        x = self.gap(x)\n        x = self.flatten(x)\n        logits = self.linear_relu_stack(x)\n        return logits","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T14:59:47.313991Z","iopub.execute_input":"2026-09-12T14:59:47.314478Z","iopub.status.idle":"2026-09-12T14:59:47.32171Z","shell.execute_reply.started":"2026-09-12T14:59:47.314449Z","shell.execute_reply":"2026-09-12T14:59:47.320912Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Обучение лёгкой модели","metadata":{}},{"cell_type":"code","source":"light_model = LightModel(5).to(device)\nprint(f'Число параметров mobile модели: {sum(x.numel() for x in light_model.parameters())}')\nprint(f'Обучаемые параметры: {sum(x.numel() for x in light_model.parameters() if x.requires_grad)}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T14:59:48.358707Z","iopub.execute_input":"2026-09-12T14:59:48.359093Z","iopub.status.idle":"2026-09-12T14:59:48.374817Z","shell.execute_reply.started":"2026-09-12T14:59:48.359066Z","shell.execute_reply":"2026-09-12T14:59:48.373981Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"lr = 1e-3\ntotal_epochs = 15\nwarmup_steps = 2\noptimizer = torch.optim.AdamW(light_model.parameters(), lr=lr)\n\nlinear_lr = LinearLR(optimizer, 1e-2, 1, total_iters=warmup_steps)\ncosine_lr = CosineAnnealingLR(optimizer, T_max=total_epochs - warmup_steps)\nscheduler = SequentialLR(optimizer, schedulers=[linear_lr, cosine_lr], milestones=[warmup_steps])\nloss_fn = nn.CrossEntropyLoss()\n\nlogging_steps = 10\ntrain_losses = []\ntest_losses = []","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T15:00:23.74718Z","iopub.execute_input":"2026-09-12T15:00:23.747613Z","iopub.status.idle":"2026-09-12T15:00:23.753141Z","shell.execute_reply.started":"2026-09-12T15:00:23.747584Z","shell.execute_reply":"2026-09-12T15:00:23.752256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for i in range(total_epochs):\n    print(f\"====Epoch {i + 1}====\")\n    train_loop(light_model, loss_fn, optimizer, train_loader, logging_steps, train_losses)\n    print('\\nTest metrics:')\n    test_loop(light_model, loss_fn, test_loader, test_losses)\n    scheduler.step()\n    print()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T15:00:23.888435Z","iopub.execute_input":"2026-09-12T15:00:23.888828Z","iopub.status.idle":"2026-09-12T16:09:03.692213Z","shell.execute_reply.started":"2026-09-12T15:00:23.888806Z","shell.execute_reply":"2026-09-12T16:09:03.691351Z"},"scrolled":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.save(light_model.state_dict(), 'model_weights.pth')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-12T16:15:02.296351Z","iopub.execute_input":"2026-09-12T16:15:02.296799Z","iopub.status.idle":"2026-09-12T16:15:02.31616Z","shell.execute_reply.started":"2026-09-12T16:15:02.296769Z","shell.execute_reply":"2026-09-12T16:15:02.315367Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}