{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.12.13"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":16017,"sourceType":"competition"}],"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"},"papermill":{"default_parameters":{},"duration":74.977322,"end_time":"2026-09-14T09:17:16.06222+00:00","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2026-09-14T09:16:01.084898+00:00","version":"2.7.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Решение с логистической регрессией на PyTorch","metadata":{}},{"cell_type":"markdown","source":"Импорт библиотек","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nimport torch\nfrom torch import nn\nfrom torch.utils.data import DataLoader, TensorDataset","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:05.345276Z","iopub.status.busy":"2026-09-14T09:16:05.344797Z","iopub.status.idle":"2026-09-14T09:16:11.745053Z","shell.execute_reply":"2026-09-14T09:16:11.744089Z"},"papermill":{"duration":6.40805,"end_time":"2026-09-14T09:16:11.747287+00:00","exception":false,"start_time":"2026-09-14T09:16:05.339237+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Загружаем датасет для обучения","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(\"/kaggle/input/competitions/Kannada-MNIST/train.csv\")","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:11.758515Z","iopub.status.busy":"2026-09-14T09:16:11.757393Z","iopub.status.idle":"2026-09-14T09:16:15.218221Z","shell.execute_reply":"2026-09-14T09:16:15.217369Z"},"papermill":{"duration":3.468804,"end_time":"2026-09-14T09:16:15.220389+00:00","exception":false,"start_time":"2026-09-14T09:16:11.751585+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.head()","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:15.231269Z","iopub.status.busy":"2026-09-14T09:16:15.23042Z","iopub.status.idle":"2026-09-14T09:16:15.266316Z","shell.execute_reply":"2026-09-14T09:16:15.265394Z"},"papermill":{"duration":0.043778,"end_time":"2026-09-14T09:16:15.268562+00:00","exception":false,"start_time":"2026-09-14T09:16:15.224784+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Итого в каждой строке мы видим свернутые в вектор 28*28 изображения.\n\nРазвернем обратно в рисуночное представление методом reshape...","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(2, 5)\nfig.suptitle(\"Цифры kannada\")\n\nfor target in range(10):\n    sample = df[df['label'] == target].iloc[0, 1:].values.reshape(28, 28)\n    ax = axes[target // 5][target % 5]\n    ax.imshow(sample, cmap='gray')\n    ax.set_title(f'Цифра {target}')\n    ax.axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:15.280101Z","iopub.status.busy":"2026-09-14T09:16:15.279276Z","iopub.status.idle":"2026-09-14T09:16:16.345244Z","shell.execute_reply":"2026-09-14T09:16:16.344123Z"},"papermill":{"duration":1.073979,"end_time":"2026-09-14T09:16:16.347306+00:00","exception":false,"start_time":"2026-09-14T09:16:15.273327+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Подготовка датасета для обучения и валидации","metadata":{}},{"cell_type":"code","source":"X, y = df.iloc[:, 1:].values, df.iloc[:, 0].values","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:16.36018Z","iopub.status.busy":"2026-09-14T09:16:16.359781Z","iopub.status.idle":"2026-09-14T09:16:16.365266Z","shell.execute_reply":"2026-09-14T09:16:16.364108Z"},"papermill":{"duration":0.014992,"end_time":"2026-09-14T09:16:16.367318+00:00","exception":false,"start_time":"2026-09-14T09:16:16.352326+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2)","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:16.378918Z","iopub.status.busy":"2026-09-14T09:16:16.378436Z","iopub.status.idle":"2026-09-14T09:16:16.985608Z","shell.execute_reply":"2026-09-14T09:16:16.984621Z"},"papermill":{"duration":0.615621,"end_time":"2026-09-14T09:16:16.987906+00:00","exception":false,"start_time":"2026-09-14T09:16:16.372285+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Превратим полученные массивы в тензор для подачи на вход нейронной сети","metadata":{}},{"cell_type":"code","source":"train_pixels = torch.tensor(X_train, dtype=torch.float32) / 255.0\ntrain_labels = torch.tensor(y_train, dtype=torch.long)","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:17.001272Z","iopub.status.busy":"2026-09-14T09:16:17.000097Z","iopub.status.idle":"2026-09-14T09:16:17.204717Z","shell.execute_reply":"2026-09-14T09:16:17.203754Z"},"papermill":{"duration":0.213956,"end_time":"2026-09-14T09:16:17.207118+00:00","exception":false,"start_time":"2026-09-14T09:16:16.993162+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_pixels = torch.tensor(X_val, dtype=torch.float32) / 255.0\nval_labels = torch.tensor(y_val, dtype=torch.long)","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:17.219616Z","iopub.status.busy":"2026-09-14T09:16:17.219256Z","iopub.status.idle":"2026-09-14T09:16:17.264244Z","shell.execute_reply":"2026-09-14T09:16:17.263373Z"},"papermill":{"duration":0.053566,"end_time":"2026-09-14T09:16:17.266474+00:00","exception":false,"start_time":"2026-09-14T09:16:17.212908+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Чтобы обучать модель нейросетки, нужно подавать данные на ее вход итеративно батч-пакетами.\n\nДля этого воспользуемся специальной оберткой TensorDataset, а от полученного датасета, в свою очередь, создадим загрузчик с заданным количеством батчей и опцией shuffle (она нужна для перемешивания датасета при создании батчей для сбалансированности классов)","metadata":{}},{"cell_type":"code","source":"train_dataset = TensorDataset(train_pixels, train_labels)\ntrain_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:17.278908Z","iopub.status.busy":"2026-09-14T09:16:17.277835Z","iopub.status.idle":"2026-09-14T09:16:17.283452Z","shell.execute_reply":"2026-09-14T09:16:17.282312Z"},"papermill":{"duration":0.013903,"end_time":"2026-09-14T09:16:17.285571+00:00","exception":false,"start_time":"2026-09-14T09:16:17.271668+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Датасет для теста и валидации перемешивать не нужно, т.к. сетка здесь не учится, и выполнять прогноз нужно строго в заданной последовательности меток","metadata":{}},{"cell_type":"code","source":"val_dataset = TensorDataset(val_pixels, val_labels)\nval_loader = DataLoader(val_dataset, batch_size=64, shuffle=False)","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:17.297205Z","iopub.status.busy":"2026-09-14T09:16:17.2965Z","iopub.status.idle":"2026-09-14T09:16:17.301597Z","shell.execute_reply":"2026-09-14T09:16:17.300712Z"},"papermill":{"duration":0.01329,"end_time":"2026-09-14T09:16:17.303811+00:00","exception":false,"start_time":"2026-09-14T09:16:17.290521+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Обучение модели","metadata":{}},{"cell_type":"markdown","source":"Создадим простую модель нейронной сети, в которой один линейный слой, преобразующий вектор размерности 28*28 (783) в вектор размерности 10 - то есть получающий \"логиты\" для каждого из 10 классов.\n\nПо сути это сырые значения с нейрона. Далее нам нужно будет преобразовать их в вероятности (например, через Softmax), но т.к. мы используем бинарную кросс-энтропию, в которой уже есть софтмакс, то оставим логиты сырыми","metadata":{}},{"cell_type":"code","source":"class NeuralNetwork(nn.Module):\n    def __init__(self,):\n        super().__init__()\n        self.flatten = nn.Flatten()\n        self.linear = nn.Sequential(\n            nn.Linear(28 * 28, 10)\n        )\n\n    def forward(self, x):\n        logits = self.linear(x)\n        return logits","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:17.315274Z","iopub.status.busy":"2026-09-14T09:16:17.314884Z","iopub.status.idle":"2026-09-14T09:16:17.32113Z","shell.execute_reply":"2026-09-14T09:16:17.320151Z"},"papermill":{"duration":0.014676,"end_time":"2026-09-14T09:16:17.323309+00:00","exception":false,"start_time":"2026-09-14T09:16:17.308633+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = NeuralNetwork()","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:17.335231Z","iopub.status.busy":"2026-09-14T09:16:17.334847Z","iopub.status.idle":"2026-09-14T09:16:17.346952Z","shell.execute_reply":"2026-09-14T09:16:17.345916Z"},"papermill":{"duration":0.021021,"end_time":"2026-09-14T09:16:17.349292+00:00","exception":false,"start_time":"2026-09-14T09:16:17.328271+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Определим в качестве функции потерь бинарную кросс-энтропию, а в качестве оптимизатора - стохастический градиентный спуск","metadata":{}},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.SGD(model.parameters(), lr=0.005)","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:17.361111Z","iopub.status.busy":"2026-09-14T09:16:17.360639Z","iopub.status.idle":"2026-09-14T09:16:23.50483Z","shell.execute_reply":"2026-09-14T09:16:23.503939Z"},"papermill":{"duration":6.152901,"end_time":"2026-09-14T09:16:23.507107+00:00","exception":false,"start_time":"2026-09-14T09:16:17.354206+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_losses = []\nval_losses = []\nepochs = []\nnum_epochs = 50","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:23.519036Z","iopub.status.busy":"2026-09-14T09:16:23.51829Z","iopub.status.idle":"2026-09-14T09:16:23.523287Z","shell.execute_reply":"2026-09-14T09:16:23.522338Z"},"papermill":{"duration":0.013464,"end_time":"2026-09-14T09:16:23.525454+00:00","exception":false,"start_time":"2026-09-14T09:16:23.51199+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Обучим и провалидируем модель в течение заданного количества эпох и нарисуем графики изменения значений loss-функции по эпохам для трейна и валидации","metadata":{}},{"cell_type":"code","source":"plt.subplot()\n\nfor epoch in range(num_epochs):\n    train_loss = 0.0\n    model.train()\n\n    for images, labels in train_loader:\n        logits = model.forward(images)\n        loss = criterion(logits, labels)\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        train_loss += loss.item() * images.size(0)\n\n    train_losses.append(train_loss / len(train_loader.dataset))\n\n    model.eval()\n    val_loss = 0.0\n    with torch.no_grad():\n        for images, labels in val_loader:\n            logits = model.forward(images)\n            loss = criterion(logits, labels)\n            val_loss += loss.item() * images.size(0)\n\n        val_losses.append(val_loss / len(val_loader.dataset))\n\n    print(\n        f\"Epoch: {epoch + 1}/{num_epochs}, train loss = {train_losses[-1]:.3f}, val loss = {val_losses[-1]:.3f}\"\n    )\n\nplt.plot(range(1, num_epochs + 1), train_losses, label='Train loss')\nplt.plot(range(1, num_epochs + 1), val_losses, label='Validation loss')\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Графики функции потерь при обучении и валидации\")\nplt.show()","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:16:23.537168Z","iopub.status.busy":"2026-09-14T09:16:23.536751Z","iopub.status.idle":"2026-09-14T09:17:14.090626Z","shell.execute_reply":"2026-09-14T09:17:14.089536Z"},"papermill":{"duration":50.562393,"end_time":"2026-09-14T09:17:14.092871+00:00","exception":false,"start_time":"2026-09-14T09:16:23.530478+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Переобучения не наблюдается, с 30-й эпохи кривые выходят на плато","metadata":{}},{"cell_type":"markdown","source":"Посмотрим на параметры модели","metadata":{}},{"cell_type":"code","source":"for name, param in model.named_parameters():\n    print(f\"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \\n\")","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:17:14.110072Z","iopub.status.busy":"2026-09-14T09:17:14.109726Z","iopub.status.idle":"2026-09-14T09:17:14.136159Z","shell.execute_reply":"2026-09-14T09:17:14.134801Z"},"papermill":{"duration":0.037386,"end_time":"2026-09-14T09:17:14.13828+00:00","exception":false,"start_time":"2026-09-14T09:17:14.100894+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Прогнозирование","metadata":{}},{"cell_type":"code","source":"test_df = pd.read_csv(\"/kaggle/input/competitions/Kannada-MNIST/test.csv\")","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:17:14.155568Z","iopub.status.busy":"2026-09-14T09:17:14.155209Z","iopub.status.idle":"2026-09-14T09:17:14.414245Z","shell.execute_reply":"2026-09-14T09:17:14.413389Z"},"papermill":{"duration":0.270367,"end_time":"2026-09-14T09:17:14.416435+00:00","exception":false,"start_time":"2026-09-14T09:17:14.146068+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df.head()","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:17:14.433595Z","iopub.status.busy":"2026-09-14T09:17:14.433219Z","iopub.status.idle":"2026-09-14T09:17:14.449678Z","shell.execute_reply":"2026-09-14T09:17:14.448723Z"},"papermill":{"duration":0.027751,"end_time":"2026-09-14T09:17:14.451834+00:00","exception":false,"start_time":"2026-09-14T09:17:14.424083+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_pixels = torch.tensor(test_df.iloc[:, 1:].values, dtype=torch.float32)\ntest_dataset = TensorDataset(test_pixels)\ntest_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:17:14.469247Z","iopub.status.busy":"2026-09-14T09:17:14.468879Z","iopub.status.idle":"2026-09-14T09:17:14.477224Z","shell.execute_reply":"2026-09-14T09:17:14.476284Z"},"papermill":{"duration":0.019649,"end_time":"2026-09-14T09:17:14.479456+00:00","exception":false,"start_time":"2026-09-14T09:17:14.459807+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:17:14.497831Z","iopub.status.busy":"2026-09-14T09:17:14.497458Z","iopub.status.idle":"2026-09-14T09:17:14.503664Z","shell.execute_reply":"2026-09-14T09:17:14.502814Z"},"papermill":{"duration":0.017562,"end_time":"2026-09-14T09:17:14.505715+00:00","exception":false,"start_time":"2026-09-14T09:17:14.488153+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_preds = []\nwith torch.no_grad():\n    for (images,) in test_loader:\n        logits = model.forward(images)\n        preds = logits.argmax(dim=1)\n        all_preds.append(preds)\n\nall_preds = torch.cat(all_preds)","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:17:14.523432Z","iopub.status.busy":"2026-09-14T09:17:14.523042Z","iopub.status.idle":"2026-09-14T09:17:14.577464Z","shell.execute_reply":"2026-09-14T09:17:14.576611Z"},"papermill":{"duration":0.066207,"end_time":"2026-09-14T09:17:14.579802+00:00","exception":false,"start_time":"2026-09-14T09:17:14.513595+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Сформируем итоговый csv-файл с прогнозами для тестового датасета, метки класса-таргета в котором неизвестны","metadata":{}},{"cell_type":"code","source":"submission = pd.DataFrame({\n    \"id\": range(len(all_preds)),\n    \"label\": all_preds.numpy()\n})\nsubmission.to_csv(\"submission.csv\", index=False)","metadata":{"execution":{"iopub.execute_input":"2026-09-14T09:17:14.597621Z","iopub.status.busy":"2026-09-14T09:17:14.597253Z","iopub.status.idle":"2026-09-14T09:17:14.612686Z","shell.execute_reply":"2026-09-14T09:17:14.611771Z"},"papermill":{"duration":0.027144,"end_time":"2026-09-14T09:17:14.614972+00:00","exception":false,"start_time":"2026-09-14T09:17:14.587828+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"papermill":{"duration":0.007799,"end_time":"2026-09-14T09:17:14.630788+00:00","exception":false,"start_time":"2026-09-14T09:17:14.622989+00:00","status":"completed"},"tags":[]},"outputs":[],"execution_count":null}]}