{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":16017,"databundleVersionId":708189,"sourceType":"competition"}],"dockerImageVersionId":30919,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"*Итак, в этом соревновании речь идёт о классификации изображений. Для начала рассмотрим представленные в датасете картинки, добавим аугментации, проанализировав, какие из них могут помочь, а какие только испортят ситуацию. После этого напишем архитектуру на базе ResNet (классический бейзлайн, можно, конечно, попробовать варианты посложнее, но для бейзлайна возьмём её), обучим модель и поиграем с гиперпараметрами, чтобы добиться приемлемого качества.*\n\n*Уже после написания кода я обнаружила, что ноутбук в соревнование у меня не засылается, так что качество будет проверено на валидационной выборке.*","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nimport torchvision\nimport torchvision.transforms as transforms\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\n\nfrom sklearn.model_selection import train_test_split","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:21.021837Z","iopub.execute_input":"2025-03-15T00:47:21.022177Z","iopub.status.idle":"2025-03-15T00:47:28.365638Z","shell.execute_reply.started":"2025-03-15T00:47:21.022145Z","shell.execute_reply":"2025-03-15T00:47:28.364799Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# начнём с того, что в данной задаче очень интересный формат представления изображений\n# научимся переводить его в более привычный для нас\n\ntrain_data = pd.read_csv('/kaggle/input/Kannada-MNIST/train.csv')\ntest_data = pd.read_csv('/kaggle/input/Kannada-MNIST/test.csv')\n\ntrain_data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:28.366889Z","iopub.execute_input":"2025-03-15T00:47:28.367436Z","iopub.status.idle":"2025-03-15T00:47:32.826192Z","shell.execute_reply.started":"2025-03-15T00:47:28.367399Z","shell.execute_reply":"2025-03-15T00:47:32.825261Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:32.827954Z","iopub.execute_input":"2025-03-15T00:47:32.828337Z","iopub.status.idle":"2025-03-15T00:47:32.845612Z","shell.execute_reply.started":"2025-03-15T00:47:32.828304Z","shell.execute_reply":"2025-03-15T00:47:32.844622Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data.iloc[0, 1:].values.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:32.847149Z","iopub.execute_input":"2025-03-15T00:47:32.847568Z","iopub.status.idle":"2025-03-15T00:47:32.858377Z","shell.execute_reply.started":"2025-03-15T00:47:32.847529Z","shell.execute_reply":"2025-03-15T00:47:32.857474Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# проверим, удалось ли получить ожидаемый результат такими преобразованиями\nplt.imshow((train_data.iloc[0, 1:].values).reshape(28, 28), cmap='gray');","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:32.859474Z","iopub.execute_input":"2025-03-15T00:47:32.859746Z","iopub.status.idle":"2025-03-15T00:47:33.130592Z","shell.execute_reply.started":"2025-03-15T00:47:32.859710Z","shell.execute_reply":"2025-03-15T00:47:33.129677Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_data = train_data.iloc[48000 : ]\ntrain_data = train_data.iloc[ : 48000]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:33.131482Z","iopub.execute_input":"2025-03-15T00:47:33.131814Z","iopub.status.idle":"2025-03-15T00:47:33.136538Z","shell.execute_reply.started":"2025-03-15T00:47:33.131782Z","shell.execute_reply":"2025-03-15T00:47:33.135468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# применим такую трансформацию ко всем изображениям\n\ntrain_data_y = train_data['label']\ntrain_data_y = train_data_y.reset_index(drop=True)\ntrain_data = train_data.drop(columns=['label'])\ntrain_data = train_data.iloc[:, :].values\nshp = len(train_data)\ntrain_data = train_data.reshape(shp, 28, 28)\n\nval_data_y = val_data['label']\nval_data_y = val_data_y.reset_index(drop=True)\nval_data = val_data.drop(columns=['label'])\nval_data = val_data.iloc[:, :].values\nshp = len(val_data)\nval_data = val_data.reshape(shp, 28, 28)\n\ntest_data_ID = test_data['id'].reset_index(drop=True)\ntest_data = test_data.drop(columns=['id'])\ntest_data = test_data.iloc[:, :].values\nshp = len(test_data)\ntest_data = test_data.reshape(shp, 28, 28)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:33.137510Z","iopub.execute_input":"2025-03-15T00:47:33.137779Z","iopub.status.idle":"2025-03-15T00:47:33.278841Z","shell.execute_reply.started":"2025-03-15T00:47:33.137739Z","shell.execute_reply":"2025-03-15T00:47:33.278072Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import Dataset, DataLoader\n\n# стандартный класс\nclass Image(Dataset):\n    def __init__(self, images, labels=None, transform=None):\n        self.images = images\n        self.labels = labels\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, idx):\n        image = self.images[idx]\n        image = image.astype(np.uint8)\n\n        if self.transform:\n            image = self.transform(image)\n\n        if self.labels is not None:\n            label = self.labels[idx]\n            return image, label\n        else:\n            return image","metadata":{"execution":{"iopub.status.busy":"2025-03-15T00:47:33.281088Z","iopub.execute_input":"2025-03-15T00:47:33.281359Z","iopub.status.idle":"2025-03-15T00:47:33.287249Z","shell.execute_reply.started":"2025-03-15T00:47:33.281336Z","shell.execute_reply":"2025-03-15T00:47:33.286427Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"*Теперь подумаем насчёт аугментаций. В силу специфичности написания цифр, всё, что приведёт к снижению качества изображения, может очень сильно повредить. В свою очередь, стандартные повороты, отражения и прочее потенциально приведут к значительному разнообразию в изображениях. Само собой, для обучающей и тестовой выборок будем использовать разные аугментации.*","metadata":{}},{"cell_type":"code","source":"train_transforms = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.CenterCrop(20),\n    transforms.RandomRotation(15),\n    transforms.RandomHorizontalFlip(0.5),\n    transforms.Normalize((0.5,), (0.5,))\n])\n\ntest_transforms = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize((0.5,), (0.5,))\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:33.288682Z","iopub.execute_input":"2025-03-15T00:47:33.288931Z","iopub.status.idle":"2025-03-15T00:47:33.301267Z","shell.execute_reply.started":"2025-03-15T00:47:33.288909Z","shell.execute_reply":"2025-03-15T00:47:33.300416Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = Image(train_data, train_data_y, transform=train_transforms)\nval_dataset = Image(val_data, val_data_y, transform=test_transforms)\ntest_dataset = Image(test_data, transform=test_transforms)\n\ntrain_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=64, shuffle=False, num_workers=2)\ntest_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:33.302205Z","iopub.execute_input":"2025-03-15T00:47:33.302489Z","iopub.status.idle":"2025-03-15T00:47:33.310918Z","shell.execute_reply.started":"2025-03-15T00:47:33.302467Z","shell.execute_reply":"2025-03-15T00:47:33.309953Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# убедимся, что загрузка данных отработала корректно\niter_ = iter(train_loader)\nimages, labels = next(iter_)\n\nfig, axes = plt.subplots(1, 10, figsize=(12, 4))\nfor i in range(10):\n    img = images[i].numpy().squeeze()\n    axes[i].imshow(img, cmap=\"gray\")\n    axes[i].set_title(f\"Label: {labels[i].item()}\")\n    axes[i].axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:33.312006Z","iopub.execute_input":"2025-03-15T00:47:33.312387Z","iopub.status.idle":"2025-03-15T00:47:34.075946Z","shell.execute_reply.started":"2025-03-15T00:47:33.312356Z","shell.execute_reply":"2025-03-15T00:47:34.074908Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# переходим к архитектуре; как уже говорилось ранее, мудрить слишком сильно не будем\n\nn_classes = 10\nclass BasicBlockNet(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.shortcut = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=1, padding=0, stride=1)\n        self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1, stride=1)\n        self.bn1 = nn.BatchNorm2d(num_features=32)\n        self.relu1 = nn.ReLU()\n        self.conv2 = nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=1, stride=1)\n        self.bn2 = nn.BatchNorm2d(num_features=32)\n        self.relu_common = nn.ReLU()\n        self.avgpool = nn.AdaptiveAvgPool2d((4, 4))\n        self.fc = nn.Linear(in_features=32 * 4 * 4, out_features=n_classes)\n\n    def forward(self, x):\n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = self.relu1(out)\n        out = self.conv2(out)\n        out = self.bn2(out)\n        out += self.shortcut(x)\n        out = self.relu_common(out)\n        out = self.avgpool(out)\n        out = out.view(x.shape[0], -1)\n        out = self.fc(out)\n        return out\n\nnet = BasicBlockNet()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:34.077358Z","iopub.execute_input":"2025-03-15T00:47:34.077766Z","iopub.status.idle":"2025-03-15T00:47:34.091420Z","shell.execute_reply.started":"2025-03-15T00:47:34.077733Z","shell.execute_reply":"2025-03-15T00:47:34.090579Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\nnet = net.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:34.092432Z","iopub.execute_input":"2025-03-15T00:47:34.092758Z","iopub.status.idle":"2025-03-15T00:47:34.407682Z","shell.execute_reply.started":"2025-03-15T00:47:34.092726Z","shell.execute_reply":"2025-03-15T00:47:34.406707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss()\n\ndef train_epoch(model, optimizer, train_loader):\n    loss_log = []\n    acc_log = []\n    model.train()\n    \n    for data, target in train_loader:\n        data, target = data.to(device), target.to(device)\n        optimizer.zero_grad()\n        logits = model(data)\n        loss = criterion(logits, target)\n        loss.backward()\n        optimizer.step()\n        \n        loss_log.append(loss.item())\n        \n        pred = logits.argmax(dim=1)\n        acc = (pred == target).sum() / target.shape[0]\n        acc_log.append(acc.item()) \n        \n        acc_log.append(acc.item()) \n\n    return loss_log, acc_log\n\ndef train(model, optimizer, n_epochs, train_loader, scheduler=None):\n    train_loss_log, train_acc_log = [], []\n\n    for epoch in range(n_epochs):\n        train_loss, train_acc = train_epoch(model, optimizer, train_loader)\n        \n        train_loss_log.extend(train_loss)\n        train_acc_log.extend(train_acc)\n\n        print(f\"Epoch {epoch}\")\n        print(f\" train loss: {np.mean(train_loss)}, train acc: {np.mean(train_acc)}\")\n        \n        if scheduler is not None:\n            scheduler.step()\n\n    return train_loss_log, train_acc_log","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:34.408599Z","iopub.execute_input":"2025-03-15T00:47:34.408868Z","iopub.status.idle":"2025-03-15T00:47:34.419725Z","shell.execute_reply.started":"2025-03-15T00:47:34.408846Z","shell.execute_reply":"2025-03-15T00:47:34.418596Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer = optim.Adam(net.parameters(), lr=0.005)  \ntr_loss_log, tr_acc_log = train(net, optimizer, 10, train_loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:47:34.420852Z","iopub.execute_input":"2025-03-15T00:47:34.421152Z","iopub.status.idle":"2025-03-15T00:51:02.425155Z","shell.execute_reply.started":"2025-03-15T00:47:34.421128Z","shell.execute_reply":"2025-03-15T00:51:02.424064Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# проверим качество на валидационной выборке\n\nnet.eval()\nval_accuracy = []\n\nwith torch.no_grad():\n    for data, target in train_loader:\n        data, target = data.to(device), target.to(device)\n        logits = net(data)\n        pred = logits.argmax(dim=1)\n        accuracy = (pred == target).sum() / target.shape[0]\n        val_accuracy.append(accuracy.item()) \n\nprint(np.mean(val_accuracy))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:51:02.426407Z","iopub.execute_input":"2025-03-15T00:51:02.426764Z","iopub.status.idle":"2025-03-15T00:51:22.053991Z","shell.execute_reply.started":"2025-03-15T00:51:02.426725Z","shell.execute_reply":"2025-03-15T00:51:22.052704Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# это было написано в надежде, что можно будет просто заслать submission.csv\nnet.eval()\npredictions = []\n\nwith torch.no_grad():\n    for data in test_loader:\n        data = data.to(device)\n        logits = net(data)\n        pred = logits.argmax(dim=1).cpu().numpy()\n        predictions.extend(pred)\n\nresult = pd.DataFrame({\"id\": test_data_ID, \"label\": predictions})\nresult.to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-15T00:51:22.055418Z","iopub.execute_input":"2025-03-15T00:51:22.055821Z","iopub.status.idle":"2025-03-15T00:51:22.781602Z","shell.execute_reply.started":"2025-03-15T00:51:22.055780Z","shell.execute_reply":"2025-03-15T00:51:22.780469Z"}},"outputs":[],"execution_count":null}]}