{"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":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":3004,"databundleVersionId":861823}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import torch # главный пакет pytorch: тензоры, матоперации, гпу и нампай для гпу\nimport torch.nn as nn # модуль с нейронными сетями, внутри все строительные блоки нейросети: слои, функции автивации и потерь, контейнеры\nimport torch.optim # модуль с оптимизаторами: стохастический градиентный спуск, адам, адаград и др\nimport pandas as pd # работа с данными, датафреймы, чтение файлов, фильтрация, группировка, пропуски\n\nfrom torch.utils.data import DataLoader, TensorDataset # два вспомогательных класса из модуля для организации данных\n# тензордатасет соединяет несколько тензоров в 1 объект, формирует пары\n# даталоадер разбивает на батчи, перемешивает данные перед каждой эпохой, параллельно загржает данные\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:00:58.723516Z","iopub.execute_input":"2026-05-18T22:00:58.723976Z","iopub.status.idle":"2026-05-18T22:00:58.728306Z","shell.execute_reply.started":"2026-05-18T22:00:58.723943Z","shell.execute_reply":"2026-05-18T22:00:58.727562Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MLPClassifier(nn.Module): # многослойный перцептрон, класс наследуется от ннмодуль, значит он получает все возможности путорч\n    # ннмодуль базовый класс для нейросетей, предоставляет регистрацию параметров - весов и смещений, тудивайс на переноса на гпу, автоматическое отслеживание градиентов и др\n    def __init__ (self, input_size, hidden_sizes = [512, 256, 128], dropout_rate = 0.2): # конструктор класса\n        # input_size - количество входных признаков, hidden_sizes - список скрытых слоев, dropout_rate - вер выключения нейрона \n        super(MLPClassifier, self).__init__() # супер это ссылка на родительский класс\n        layers = [] # пустой список для слоев\n        prev_size = input_size # запоминает входной размер признаков, он равен размеру предыдущего слоя\n        for size in hidden_sizes:\n            layers.append(nn.Linear(prev_size, size)) # добавляет полносвязный слой, матрица весов первсайз на сайз и вектор смещения размером сайз\n            layers.append(nn.BatchNorm1d(size)) # слой батч-нормализации\n            layers.append(nn.ReLU()) # функция автивации релу, отрицательные превращаются в ноль, положительные остаются прежними\n            layers.append(nn.Dropout(dropout_rate)) # слой регуляризации дропаут, \n            prev_size = size # обновляем вес\n            \n        layers.append(nn.Linear(prev_size, 10)) # добавляем полносвязный слой который превращает выход последнего скрытого слоя в 10 чисел\n        self.network = nn.Sequential(*layers) # контейнер из всех слоев списка, звезда чтобы подать кажджый слой как отдельный аргумент - оператор распаковки\n\n    def forward(self, x): # как данные проходят через сеть\n        return self.network(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:00:58.734887Z","iopub.execute_input":"2026-05-18T22:00:58.735463Z","iopub.status.idle":"2026-05-18T22:00:58.751664Z","shell.execute_reply.started":"2026-05-18T22:00:58.735437Z","shell.execute_reply":"2026-05-18T22:00:58.750804Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset = pd.read_csv('/kaggle/input/competitions/digit-recognizer/train.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:00:58.753219Z","iopub.execute_input":"2026-05-18T22:00:58.753577Z","iopub.status.idle":"2026-05-18T22:01:00.425857Z","shell.execute_reply.started":"2026-05-18T22:00:58.753551Z","shell.execute_reply":"2026-05-18T22:01:00.424889Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.cuda.is_available()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:00.427337Z","iopub.execute_input":"2026-05-18T22:01:00.427724Z","iopub.status.idle":"2026-05-18T22:01:00.433452Z","shell.execute_reply.started":"2026-05-18T22:01:00.427696Z","shell.execute_reply":"2026-05-18T22:01:00.432398Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device('cuda') # использовать видеокарту","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:00.435268Z","iopub.execute_input":"2026-05-18T22:01:00.435586Z","iopub.status.idle":"2026-05-18T22:01:00.446368Z","shell.execute_reply.started":"2026-05-18T22:01:00.435560Z","shell.execute_reply":"2026-05-18T22:01:00.445729Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset.shape # врзвращает размерность датафрейма в виде кортежа","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:00.447723Z","iopub.execute_input":"2026-05-18T22:01:00.448260Z","iopub.status.idle":"2026-05-18T22:01:00.461877Z","shell.execute_reply.started":"2026-05-18T22:01:00.448236Z","shell.execute_reply":"2026-05-18T22:01:00.461175Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset['label'].value_counts().sort_index() # выбираем колонку лэйбл, считаем уникальные значения и сортируем по индексу","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:00.462798Z","iopub.execute_input":"2026-05-18T22:01:00.463177Z","iopub.status.idle":"2026-05-18T22:01:00.476975Z","shell.execute_reply.started":"2026-05-18T22:01:00.463128Z","shell.execute_reply":"2026-05-18T22:01:00.476262Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset['label'].value_counts().sort_index()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:00.477995Z","iopub.execute_input":"2026-05-18T22:01:00.478361Z","iopub.status.idle":"2026-05-18T22:01:00.489960Z","shell.execute_reply.started":"2026-05-18T22:01:00.478325Z","shell.execute_reply":"2026-05-18T22:01:00.489329Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np # библиотека для матеши\nfrom sklearn.model_selection import train_test_split # функция на разделенные данных на обучающую и тестовую модель","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:00.491054Z","iopub.execute_input":"2026-05-18T22:01:00.491743Z","iopub.status.idle":"2026-05-18T22:01:00.500704Z","shell.execute_reply.started":"2026-05-18T22:01:00.491710Z","shell.execute_reply":"2026-05-18T22:01:00.500076Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X = dataset.drop('label', axis = 1).values.astype(np.float32) / 255.0 # удаляет колонку лэйбл из бд и удаляем столбец, переводим в массив, преобразуем в число с плавающей точкой и нормируем пиксель \ny = dataset['label'].values # помещаем в у колонку лэйбл и превращает в массив","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:00.501546Z","iopub.execute_input":"2026-05-18T22:01:00.501835Z","iopub.status.idle":"2026-05-18T22:01:00.687284Z","shell.execute_reply.started":"2026-05-18T22:01:00.501812Z","shell.execute_reply":"2026-05-18T22:01:00.686689Z"}},"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, random_state=123)\n# делит данные на треин и валидацию","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:00.688239Z","iopub.execute_input":"2026-05-18T22:01:00.688532Z","iopub.status.idle":"2026-05-18T22:01:01.001420Z","shell.execute_reply.started":"2026-05-18T22:01:00.688498Z","shell.execute_reply":"2026-05-18T22:01:01.000730Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train_tensor = torch.FloatTensor(X_train) # тензор с плавающей точкой\ny_train_tensor = torch.LongTensor(y_train) # тензор с целыми числами\nX_val_tensor = torch.FloatTensor(X_val)\ny_val_tensor = torch.LongTensor(y_val)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:01.003631Z","iopub.execute_input":"2026-05-18T22:01:01.003845Z","iopub.status.idle":"2026-05-18T22:01:01.007919Z","shell.execute_reply.started":"2026-05-18T22:01:01.003825Z","shell.execute_reply":"2026-05-18T22:01:01.007396Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = TensorDataset(X_train_tensor, y_train_tensor) # формируем пары для обращения\nval_dataset = TensorDataset(X_val_tensor, y_val_tensor)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:01.009146Z","iopub.execute_input":"2026-05-18T22:01:01.009536Z","iopub.status.idle":"2026-05-18T22:01:01.022447Z","shell.execute_reply.started":"2026-05-18T22:01:01.009509Z","shell.execute_reply":"2026-05-18T22:01:01.021571Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch_size = 256 ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:01.023484Z","iopub.execute_input":"2026-05-18T22:01:01.024125Z","iopub.status.idle":"2026-05-18T22:01:01.034425Z","shell.execute_reply.started":"2026-05-18T22:01:01.024093Z","shell.execute_reply":"2026-05-18T22:01:01.033805Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle = True)\nval_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle = True)\n# создает батчи передает их понемногу в нейросеть","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:01.035319Z","iopub.execute_input":"2026-05-18T22:01:01.035606Z","iopub.status.idle":"2026-05-18T22:01:01.048361Z","shell.execute_reply.started":"2026-05-18T22:01:01.035569Z","shell.execute_reply":"2026-05-18T22:01:01.047500Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = MLPClassifier(input_size=784).to(device) # входное изображение модели имеет 784 пикселя + перемещение всей модели на видеокарту\n\ncriterion = nn.CrossEntropyLoss() # функция потерь: сравнивает предсказания с правильным ответом \noptimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4) # оптимизатор адам, адаптируется скорость обучения для каждого параметра, учебный шаг 0.001 и штраф за большие веса 0.0001\n\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min') # уменьшение учебного шага при застревании модели","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:01.049264Z","iopub.execute_input":"2026-05-18T22:01:01.049573Z","iopub.status.idle":"2026-05-18T22:01:01.066629Z","shell.execute_reply.started":"2026-05-18T22:01:01.049534Z","shell.execute_reply":"2026-05-18T22:01:01.066093Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sum(p.numel() for p in model.parameters()) # общее количество обучаемых параметров в модели","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:01.067303Z","iopub.execute_input":"2026-05-18T22:01:01.067579Z","iopub.status.idle":"2026-05-18T22:01:01.072584Z","shell.execute_reply.started":"2026-05-18T22:01:01.067558Z","shell.execute_reply":"2026-05-18T22:01:01.071963Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_epoch(model, train_loader, criterion, optimizer, device):\n    model.train() # перевод модели в режим обучения\n\n    total_loss = 0 # суммарная ошибка по всем батчам\n    correct = 0 # количесов правильных предсказаний\n    total = 0 # общее количество параметров\n\n    for data, target in train_loader:\n        # передаем данные на гпу\n        data = data.to(device)\n        target = target.to(device)\n\n        optimizer.zero_grad() # обнуление градиента\n        output = model(data) # подает дата в нейросеть\n        loss = criterion(output, target) # считаем ошибку, применяем софтмакс к аутпут, берем отрицательный логарифм и усредняем по батчу\n        loss.backward() # вычисляем градиент по каждому параметру модели и сохраняем для каждого параметра\n        optimizer.step() # обновление веса: вес - учебный шаг * градиент\n\n        total_loss += loss.item() # накопление ошибки\n        pred = output.argmax(dim=1) # вычисление точности по оси классов\n        correct += pred.eq(target).sum().item() # поэлементное сравнение и считаем количество совпадений\n        total += target.size(0) # сумма по всем батчам\n\n    return total_loss, correct / total","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:01.074261Z","iopub.execute_input":"2026-05-18T22:01:01.074466Z","iopub.status.idle":"2026-05-18T22:01:01.083637Z","shell.execute_reply.started":"2026-05-18T22:01:01.074446Z","shell.execute_reply":"2026-05-18T22:01:01.083130Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def validate(model, val_loader, criterion, device):\n    model.eval() # режим валидации\n\n    total_loss = 0\n    correct = 0\n    total = 0 \n\n    with torch.no_grad(): # отключает отслеживание градиентов на время валидации\n        for data, target in val_loader:\n            data = data.to(device)\n            target = target.to(device)\n            \n            output = model(data)\n            loss = criterion(output, target)\n            \n            total_loss += loss.item()\n            pred = output.argmax(dim=1)\n            correct += pred.eq(target).sum().item()\n            total += target.size(0)\n\n    return total_loss, correct / total","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:01.084323Z","iopub.execute_input":"2026-05-18T22:01:01.084576Z","iopub.status.idle":"2026-05-18T22:01:01.094618Z","shell.execute_reply.started":"2026-05-18T22:01:01.084543Z","shell.execute_reply":"2026-05-18T22:01:01.094075Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch.cuda.empty_cache()\nfrom IPython.display import clear_output","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:01.095438Z","iopub.execute_input":"2026-05-18T22:01:01.095703Z","iopub.status.idle":"2026-05-18T22:01:01.107592Z","shell.execute_reply.started":"2026-05-18T22:01:01.095682Z","shell.execute_reply":"2026-05-18T22:01:01.106871Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loss_arr = []\ntrain_acc_arr = []\n\nval_loss_arr = []\nval_acc_arr = []","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:01.108630Z","iopub.execute_input":"2026-05-18T22:01:01.108954Z","iopub.status.idle":"2026-05-18T22:01:01.118768Z","shell.execute_reply.started":"2026-05-18T22:01:01.108932Z","shell.execute_reply":"2026-05-18T22:01:01.117924Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:01.119715Z","iopub.execute_input":"2026-05-18T22:01:01.120084Z","iopub.status.idle":"2026-05-18T22:01:01.131919Z","shell.execute_reply.started":"2026-05-18T22:01:01.120054Z","shell.execute_reply":"2026-05-18T22:01:01.131218Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_epochs = 40\nfor epoch in range(num_epochs):\n    train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer, device)\n    val_loss, val_acc = validate(model, val_loader, criterion, device)\n\n    train_loss_arr.append(train_loss)\n    train_acc_arr.append(train_acc)\n    \n    val_loss_arr.append(val_loss)\n    val_acc_arr.append(val_acc)\n\n    clear_output()\n    fig, axes = plt.subplots(1, 2, figsize=(12, 6))\n    axes[0].plot(train_loss_arr, label='train')\n    axes[0].plot(val_loss_arr, label='val')\n    axes[1].plot(train_acc_arr, label='train')\n    axes[1].plot(val_acc_arr, label='val')\n\n    plt.legend()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:01:01.132797Z","iopub.execute_input":"2026-05-18T22:01:01.133109Z","iopub.status.idle":"2026-05-18T22:01:46.086167Z","shell.execute_reply.started":"2026-05-18T22:01:01.133076Z","shell.execute_reply":"2026-05-18T22:01:46.085492Z"}},"outputs":[],"execution_count":null}]}