{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ЛАБОРАТОРНАЯ РАБОТА №2\n",
    "## МНОГОСЛОЙНЫЙ ПЕРСЕПТРОН"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> Цель работы: изучение алгоритмов обучения многослойного персептрона, выбор структуры и контроль качества обучения нейронной сети, решение задачи\n",
    "классификации многомерных данных.\n",
    ">\n",
    "> Задание\n",
    "> 1. Открыть хранящиеся в файле `min_water.csv` данные о сорока образцах минеральной воды. Проведя предварительный анализ данных, выделить из 23-х признаков наиболее информативные для классификации образцов признаки. Разделить имеющуюся выборку на три части: для обучения, для верификации, для тестирования. Сохранить исходные данные.\n",
    "> 2. Создать многослойный персептрон с одним нейроном в выходном слое. Решить задачу классификации имеющихся данных об образцах минеральной воды по пяти классам, используя при обучении алгоритм обратного распространения ошибки. Проанализировать полученные результаты.\n",
    "> 3. Создать многослойный персептрон с пятью нейронами в выходном слое и снова решить задачу классификации имеющихся данных об образцах минеральной воды по пяти классам. Проанализировать\n",
    "полученные результаты. По результатам п.2 выбрать наилучшую структуру ИНС для решения поставленной задачи.\n",
    "> 4. Решить задачу классификации образцов минеральной воды по четырем классам, предварительно пометив один из классов, как недоступный в процессе обучения. Проанализировать результаты классификации, предъявив обученной ИНС неизвестный сорт минеральной воды. Объяснить полученные результаты."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Импорт библиотек:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "executionInfo": {
     "elapsed": 4,
     "status": "ok",
     "timestamp": 1768213447856,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "FwCw1KfEpbsm"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "import torch\n",
    "import matplotlib.pyplot as plt\n",
    "from IPython.display import clear_output\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "from torch import nn\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pandas — это библиотека Python, представляющая собой «обёртку» над NumPy, специально разработанную для работы с табличными (структурированными) данными. Она предоставляет два ключевых типа данных: `Series` (одномерный массив с метками) и `DataFrame` (двумерная таблица с колонками разного типа). В отличие от «чистых» массивов NumPy, pandas позволяет: удобно индексировать данные по строкам и столбцам, обрабатывать пропуски (`NaN`), работать с разнородными типами данных в одной таблице, легко выполнять фильтрацию, группировку, агрегацию и слияния таблиц. При этом pandas сохраняет высокую производительность за счёт внутренней опоры на оптимизированные операции NumPy и C‑код.\n",
    "\n",
    "Официальная документация: https://pandas.pydata.org/docs/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Содержание:  \n",
    "[1. Подготовка данных](#p_1)  \n",
    "[2. Задача бинарной классификации](#p_2)  \n",
    "[3. Задача многоклассовой классификации](#p_3)  \n",
    "[4. Задача многоклассовой классификации (с исключением одного из классов)](#p_4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Подготовка данных<a id=\"p_1\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Загрузим в датафрейм `data` данные о сорока образцах минеральной воды, хранящиеся в файле `min_water.txt`.\n",
    "\n",
    "Первый столбец содержит разметку для задачи бинарной классификации образцов воды (метки `0`и `1`), второй столбец — разметку для задачи многоглассовой классификации (каждому из 5 сортов минеральной воды соответствуют по 5 елассов их образцов). Оставшиеся 23 столбца рассматриваются как входные признаки."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 236
    },
    "executionInfo": {
     "elapsed": 112,
     "status": "ok",
     "timestamp": 1768213447972,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "PkNWYbQCpsl_",
    "outputId": "a19eff1c-593c-47b6-ed64-0aa6f84f56c2"
   },
   "outputs": [],
   "source": [
    "data = pd.read_csv('min_water.csv')\n",
    "data.head(n=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Вынесем в отдельные переменные:\n",
    " - `y_binary` — выходной признак для задачи бинарной классификации (первый столбец датафрейма);\n",
    " - `y_multiclass` — выходной признак для задачи многоклассовой классификации (второй столбец датафрейма);\n",
    " - `X_data` — входные признаки (оставшиеся столбцы)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "executionInfo": {
     "elapsed": 17,
     "status": "ok",
     "timestamp": 1768213447974,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "D_QiYrPkpsu0"
   },
   "outputs": [],
   "source": [
    "y_binary = data.iloc[:, 0]\n",
    "y_multiclass = data.iloc[:, 1]\n",
    "\n",
    "X_data = data.iloc[:, 2:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Нам необходимо отобрать из исходных 23 признаков наиболее информативные и полезные для задач бинарной и многоклассовой классификации.\n",
    "\n",
    "Вычислим матрицу парных коэффициентов корреляции Пирсона для всех признаков в `X_data`. Это позволит нам:\n",
    "- выявить сильно коррелирующие признаки (|r| > 0,7–0,8), которые несут избыточную информацию;\n",
    "- снизить риск переобучения за счёт удаления дублирующих признаков;\n",
    "- упростить модель и ускорить обучение без потери качества;\n",
    "- понять структуру данных и взаимосвязи между признаками;\n",
    "- избежать проблем мультиколлинеарности в линейных моделях (когда коэффициенты становятся неустойчивыми из‑за сильной взаимной зависимости признаков)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 820
    },
    "executionInfo": {
     "elapsed": 27,
     "status": "ok",
     "timestamp": 1768213448004,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "-6Mo3AnWusFT",
    "outputId": "92e51428-c6a4-4e18-eab6-96e3af0a4eee"
   },
   "outputs": [],
   "source": [
    "X_data_correlation = X_data.corr(method='pearson')\n",
    "X_data_correlation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для наглядности построим тепловую карту корреляционной матрицы. Отметим на карте только те пары признаков, у которых коэффициент корреляции Пирсона больше 0.75:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 456
    },
    "executionInfo": {
     "elapsed": 369,
     "status": "ok",
     "timestamp": 1768213448385,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "usQSk41iuVuW",
    "outputId": "143b4366-59ca-48c8-ff7a-10442a2f0d24"
   },
   "outputs": [],
   "source": [
    "plt.figure(figsize=(10, 8))\n",
    "sns.heatmap(X_data_correlation > 0.75)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Выпишите в список `features` отобранные в процессе анализа признаки (формат: `features = ['VAR1', 'VAR2']`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "features = # Ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Датафрейм с отобранными входными признаками `X_data_filtered`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_data_filtered = X_data.loc[:, features]\n",
    "X_data_filtered.head(n=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Метрика accuracy (доля правильно классифицированных примеров от общего числа) будет общей и для задачи бинарной классификации, и для задачи многоклассовой классификации:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "executionInfo": {
     "elapsed": 21,
     "status": "ok",
     "timestamp": 1768213448636,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "vcEZHV2G1fSQ"
   },
   "outputs": [],
   "source": [
    "def accuracy(y_pred, y_true):\n",
    "    return torch.sum(y_pred == y_true) / len(y_true)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Задача бинарной классификации<a id=\"p_2\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Изучите [документацию](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) к функции `train_test_split`, чтобы разделить исходные данные на обучающую, валидационную и тестовую выборки — стандартный этап подготовки данных для обучения и оценки моделей машинного обучения.\n",
    "\n",
    "В первом её вызове отделите, например, 20% исходных данных (`X_data_filtered`, `y_binary`) в тестовую выборку (`X_binary_test`, `y_binary_test`), 80% оставьте для обучения (`X_binary_train`, `y_binary_train`). Через параметр `stratify` сохраните соотношение классов в обеих выборках.\n",
    "\n",
    "Во втором вызове отделите 20% данных для обучения в тестовую выборку (`X_binary_valid`, `y_binary_valid`), 80% оставьте для финального обучения (`X_binary_train`, `y_binary_train`). Не забудьте про параметр `stratify`.\n",
    "\n",
    "*Примечание*. Валидационная выборка помогает подбирать гиперпараметры (например, число эпох, скорость обучения) и следить за переобучением. Тестовая выборка «заморожена» до конца обучения и используется только для итоговой оценки, чтобы избежать смещения в оценке качества. Их соотношение с обучающей выборкой может быть и иным."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_binary_train, X_binary_test, y_binary_train, y_binary_test = # Ваш код здесь\n",
    "\n",
    "X_binary_train, X_binary_valid, y_binary_train, y_binary_valid = # Ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Входные признаки для нейронной сети необходимо либо нормализовывать, либо стандартизировать — чтобы привести их к единому масштабу. Это ускоряет сходимость градиентного спуска и делает обучение стабильнее (спуск движется более прямо к минимуму, требует меньше итераций для сходимости, менее подвержен «взрыву» или «исчезновению» градиентов).\n",
    "\n",
    "Нормализация (Min‑Max Scaling) приводит значения признака к фиксированному диапазону (обычно `[0, 1]`):\n",
    "      \n",
    "$$\n",
    "x_{\\text{norm}} = \\frac{x - x_{\\min}}{x_{\\max} - x_{\\min}}\n",
    "$$  \n",
    "где:  \n",
    "- $x$ — исходное значение признака;  \n",
    "- $x_{\\min}$ — минимальное значение признака в выборке;  \n",
    "- $x_{\\max}$ — максимальное значение признака в выборке.\n",
    "\n",
    "\n",
    "Стандартизация  преобразует распределение признака к среднему `0` и стандартному отклонению `1`:  \n",
    "$$\n",
    "x_{\\text{standard}} = \\frac{x - \\mu}{\\sigma}\n",
    "$$  \n",
    "где:  \n",
    "- $x$ — исходное значение признака;  \n",
    "- $\\mu$ — среднее значение признака в выборке;  \n",
    "- $\\sigma$ — стандартное отклонение признака в выборке."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Произведём стандартизацию входных признаков (но по аналогии можете произвести нормализацию):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_means = X_binary_train.mean(axis=0)\n",
    "X_stds = X_binary_train.std(axis=0, ddof=1)\n",
    "\n",
    "X_binary_train = # Ваш код здесь\n",
    "X_binary_valid = # Ваш код здесь\n",
    "X_binary_test = # Ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "rP2B8eJk2sYX"
   },
   "source": [
    "Представим данные обучающей, валидационной и тестовой выборок в виде тензоров:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "executionInfo": {
     "elapsed": 54,
     "status": "ok",
     "timestamp": 1768213448693,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "jsWiE7Ke1p9S"
   },
   "outputs": [],
   "source": [
    "X_binary_train = torch.tensor(X_binary_train.values).float()\n",
    "X_binary_valid = torch.tensor(X_binary_valid.values).float()\n",
    "X_binary_test = torch.tensor(X_binary_test.values).float()\n",
    "\n",
    "y_binary_train = torch.tensor(y_binary_train.values).reshape(-1, 1).float()\n",
    "y_binary_valid = torch.tensor(y_binary_valid.values).reshape(-1, 1).float()\n",
    "y_binary_test = torch.tensor(y_binary_test.values).reshape(-1, 1).float()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Создайте экземпляр нейронной сети для решения задачи бинарной классификации — например, через `nn.Sequential()`, как было показано в первой лабораторной работе.\n",
    "\n",
    "Важно соблюсти два ключевых условия при проектировании архитектуры:\n",
    "- число входных признаков в первом слое (`in_features`) должно точно соответствовать размерности признаков в обучающих данных (количеству столбцов в `X_binary_train`) (воспользуйтесь свойством `X_binary_train.shape`);\n",
    "- в выходном слое необходимо использовать один нейрон с сигмоидальной функцией активации  — напомним, что это гарантирует, что сеть будет выдавать значение в диапазоне [0, 1], интерпретируемое как вероятность принадлежности к положительному классу в задаче бинарной классификации."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_binary = nn.Sequential(\n",
    "    # Ваш код здесь\n",
    ")\n",
    "\n",
    "model_binary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Удостоверимся, что наша сеть работает — рассчитаем по её выходу, который представляет собой предсказанные вероятности принадлежности к классу 1:\n",
    " - значение функции потерь (бинарная кросс-энтропия);\n",
    " - метрику accuracy."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 22,
     "status": "ok",
     "timestamp": 1768213448710,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "zP1t3NsT3xiH",
    "outputId": "b722665a-ee83-4393-b45a-457076a3ac29"
   },
   "outputs": [],
   "source": [
    "y_binary_train_prob = model_binary(X_binary_train)\n",
    "\n",
    "criterion = nn.BCELoss()\n",
    "print(f'Loss: {criterion(y_binary_train_prob, y_binary_train).item():.6f}')\n",
    "print(f'Accuracy: {accuracy((y_binary_train_prob > 0.5).float(), y_binary_train).item():.3f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 17,
     "status": "ok",
     "timestamp": 1768213448728,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "tfLMe06S5m4V",
    "outputId": "281962d3-e691-439e-9b39-bd463c7ad8a8"
   },
   "source": [
    "Теперь перейдём к обучению нейронной сети для решения поставленной задачи бинарной классификации.\n",
    "\n",
    "Зададим \n",
    " - экземпляр нейронной сети;\n",
    " - количество эпох или итераций в процессе обучения;\n",
    " - скорость обучение и коэффициент импульса;\n",
    " - оптимизатор;\n",
    " - функцию потерь (поскольку решается задача бинарной классификации, выберем бинарную кросс-энтропию `nn.BCELoss()`).\n",
    " \n",
    " \n",
    "*Примечание*. Вы можете использовать для обучения сети либо код ниже, либо модифицированный на его основе — например, реализовать функцию обучения нейронной сети и проверки её работы."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "executionInfo": {
     "elapsed": 9,
     "status": "ok",
     "timestamp": 1768213448730,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "z-pABN4e6bAf"
   },
   "outputs": [],
   "source": [
    "# Перебор seed для инициализации параметров\n",
    "torch.manual_seed(seed=42)\n",
    "\n",
    "model_binary = # Ваш код здесь\n",
    "\n",
    "epochs = # Ваш код здесь\n",
    "\n",
    "learning_rate = # Ваш код здесь\n",
    "momentum = # Ваш код здесь\n",
    "\n",
    "optimizer = # Ваш код здесь\n",
    "criterion = nn.BCELoss()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Обучение нейронной сети для решения задачи бинарной классификации:**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 504
    },
    "executionInfo": {
     "elapsed": 22752,
     "status": "ok",
     "timestamp": 1768213471478,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "qKimvoStsZXv",
    "outputId": "e903f88f-542f-4ab7-c291-2be72f726d39"
   },
   "outputs": [],
   "source": [
    "loss_train_history, loss_valid_history = [], []\n",
    "\n",
    "for epoch in range(epochs):\n",
    "    model_binary.train()\n",
    "\n",
    "    optimizer.zero_grad()\n",
    "\n",
    "    y_binary_train_prob = model_binary(X_binary_train)\n",
    "\n",
    "    loss_train = criterion(y_binary_train_prob, y_binary_train)\n",
    "    loss_train_history.append(loss_train.item())\n",
    "\n",
    "    loss_train.backward()\n",
    "\n",
    "    optimizer.step()\n",
    "\n",
    "    model_binary.eval()\n",
    "    \n",
    "    # Отключаем градиенты для этапа валидации\n",
    "    with torch.no_grad():\n",
    "        y_binary_valid_prob = model_binary(X_binary_valid)        \n",
    "        loss_valid = criterion(y_binary_valid_prob, y_binary_valid)\n",
    "        \n",
    "    loss_valid_history.append(loss_valid.item())\n",
    "\n",
    "    if (epoch + 1) % 5 == 0:\n",
    "\n",
    "        clear_output(True)\n",
    "        plt.plot(range(1, epoch+2), loss_train_history, label='Train', color='green')\n",
    "        plt.plot(range(1, epoch+2), loss_valid_history, label='Valid', color='red')\n",
    "        plt.title(f'Epoch: {epoch + 1}, Loss Train: {loss_train_history[-1]:.6f}, Loss Valid: {loss_valid_history[-1]:.6f}')\n",
    "        plt.grid(True, alpha=0.3)\n",
    "        plt.legend(loc='best')\n",
    "        plt.show()\n",
    "        \n",
    "        print('Accuracy')\n",
    "        print(f'Train: {accuracy((y_binary_train_prob > 0.5).float(), y_binary_train).item():.3f}')\n",
    "        print(f'Valid: {accuracy((y_binary_valid_prob > 0.5).float(), y_binary_valid).item():.3f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "После того, как вы подобрали параметры и добились приемлемого качества работы сети на обучающей и валидационной выборках, в качестве финального шага проверьте работу сети на отложенной тестовой выборке:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 35,
     "status": "ok",
     "timestamp": 1768213471518,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "V__6qHAe-7VS",
    "outputId": "61ab4f43-fc4d-4a07-d1a9-921f91a2d0fc"
   },
   "outputs": [],
   "source": [
    "with torch.no_grad():\n",
    "    y_binary_test_prob = model_binary(X_binary_test)\n",
    "    loss_test = criterion(y_binary_test_prob, y_binary_test)\n",
    "    \n",
    "print(f'Loss Test: {loss_test.item():.6f}')\n",
    "print(f'Accuracy Test: {accuracy((y_binary_valid_prob > 0.5).float(), y_binary_valid).item():.3f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "executionInfo": {
     "elapsed": 30,
     "status": "ok",
     "timestamp": 1768213471544,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "ZtJ4WeKCATqU"
   },
   "source": [
    "Если качество схожее, нейронная сеть не переобучена.\n",
    "\n",
    "Поэкспериметируйте — сделайте несколько вариантов с разными архитектурами сети, оптимизаторами, значениями скорости обучения и коэффициента импульса. Выберите лучшие по качеству обучения и количеству эпох."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Задача многоклассовой классификации<a id=\"p_3\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для решения задачи многоклассовой классификации по аналогии с задачей бинарной классификации:\n",
    " - с помощью функции `train_test_split` разбейте данные (`X_data_filtered`, `y_multiclass`) на обучающую (`X_multiclass_train`, `y_multiclass_train`), валидационную (`X_multiclass_valid`, `y_multiclass_valid`) и тестовую выборки (`X_multiclass_test`, `y_multiclass_test`) с сохранением соотншений классов (сортов минеральной воды);\n",
    " - стандартизируйте или нормализуйте входные признаки;\n",
    " - из векторов с выходным признаком вычтите единицу и представьте их в виде тензоров целочисленного формата (метод `.long()`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_multiclass_train, X_multiclass_test, y_multiclass_train, y_multiclass_test = # Ваш код здесь\n",
    "\n",
    "X_multiclass_train, X_multiclass_valid, y_multiclass_train, y_multiclass_valid = # Ваш код здесь"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 25,
     "status": "ok",
     "timestamp": 1768213471611,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "CsGt0n6-B5WA",
    "outputId": "c555da32-2652-48a2-8954-21b76ffaa019"
   },
   "source": [
    "Для задач многоклассовой классификации в машинном обучении применяют функцию активации `softmax`, которая посредством нормализации преобразует вектор логитов (выходов последнего слоя) в распределение вероятностей:  \n",
    "     $$\n",
    "     p_k = \\frac{e^{z_k}}{\\sum_{k=1}^{K} e^{z_k}},\n",
    "     $$  \n",
    "     где $z_k$ — выход для $k$-го класса, $K$ — число классов.  \n",
    "   - Свойства:  \n",
    "     - все выходы ∈ [0, 1];  \n",
    "     - сумма выходов = 1;  \n",
    "     - максимальный выход соответствует наиболее вероятному классу.\n",
    "\n",
    "Реализованная в PyTorch функция потерь `nn.CrossEntropyLoss()` объединяет в себе `log_softmax` (численно устойчивая версия softmax + логарифм) и `NLLLoss` (отрицательная логарифмическая правдоподобность). Она принимает на вход именно логиты.\n",
    "\n",
    "Создадим экземпляр нейронной сети для решения задачи многоклассовой классификации — например, также через `nn.Sequential()`:\n",
    " - число входных признаков в первом слое (`in_features`) должно точно соответствовать размерности признаков в обучающих данных (количеству столбцов в `X_binary_train`) (воспользуйтесь свойством `X_binary_train.shape`);\n",
    " - количество нейроной в выходном слое должно совпадать с количеством классов;\n",
    " - в связи с тем, что сеть нам должна выдавать логиты, функцию активации на выходном слое можно не использовать (хотя можно взять `nn.ReLU()`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 9,
     "status": "ok",
     "timestamp": 1768213471622,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "l2wN73_-BlzP",
    "outputId": "b630d430-2215-41f3-9a62-93db01a5207a"
   },
   "outputs": [],
   "source": [
    "model_multiclass = nn.Sequential(\n",
    "    nn.Linear(in_features=X_multiclass_train.shape[1], out_features=y_multiclass.nunique()),\n",
    "    nn.ReLU()\n",
    ")\n",
    "\n",
    "model_multiclass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Пропустив данные через эту сеть, получим логиты, представляющие собою вектора, чья длина равна количеству классов:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 21,
     "status": "ok",
     "timestamp": 1768213471644,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "4BTXxrftCJzK",
    "outputId": "e3568629-6f98-4b92-ef3b-92c607a92f9b"
   },
   "outputs": [],
   "source": [
    "y_multiclass_train_logits = model_multiclass(X_multiclass_train)\n",
    "y_multiclass_train_logits[:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверим работу функции потерь с ними:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 15,
     "status": "ok",
     "timestamp": 1768213471707,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "4ZQizLNZAT8_",
    "outputId": "113bf8b1-8c80-4a82-d09a-7db97eb2222f"
   },
   "outputs": [],
   "source": [
    "criterion = nn.CrossEntropyLoss()\n",
    "criterion(y_multiclass_train_logits, y_multiclass_train)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Отметим, что для задачи классификации нормализация через `softmax` не требуется — в качестве ответа (метки класса) берем индекс максимального элемента в логите:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 8,
     "status": "ok",
     "timestamp": 1768213471654,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "RBufWh2CDW1P",
    "outputId": "7c14a8d4-9306-429f-b870-50337e29ccaa"
   },
   "outputs": [],
   "source": [
    "y_multiclass_train_logits.argmax(dim=1)[:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Метрика accuracy:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 48,
     "status": "ok",
     "timestamp": 1768213471704,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "Cz58yMsGDYmx",
    "outputId": "8c93fa6f-09d5-43b6-dadf-67022c236dc5"
   },
   "outputs": [],
   "source": [
    "accuracy(y_multiclass_train_logits.argmax(dim=1), y_multiclass_train).item()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По аналогии с задачей бинарной классификации создайте, обучите и проверьте нейронную сеть для решения задачи многоклассовой классификации:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "executionInfo": {
     "elapsed": 6,
     "status": "ok",
     "timestamp": 1768213471720,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "V1CgE0O7Lz0g"
   },
   "outputs": [],
   "source": [
    "# Ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "3lHwSPaz35VQ"
   },
   "source": [
    "Поэкспериметируйте — сделайте несколько вариантов с разными архитектурами сети, оптимизаторами, значениями скорости обучения и коэффициента импульса. Выберите лучшие по качеству обучения и количеству эпох."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "1RTQgj4gLz5O"
   },
   "source": [
    "## 4. Задача многоклассовой классификации (с исключением одного из классов)<a id=\"p_4\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Введите метку класса, который хотите исключить (1, 2, 3, 4 или 5):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "label_to_exclude = # Ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Маска (логическое условие) исключения в выходном признаке меток с этим классом:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 1000
    },
    "executionInfo": {
     "elapsed": 83,
     "status": "ok",
     "timestamp": 1768217647959,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "Ge_M2-suvOfq",
    "outputId": "7562cae1-5c7c-4ee8-c013-6428ec959db6"
   },
   "outputs": [],
   "source": [
    "mask_to_exclude = y_multiclass != label_to_exclude\n",
    "y_multiclass[mask_to_exclude]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "3jVGVtJGw3Du"
   },
   "source": [
    "Значения входных признаков, которые соответствуют исключённому классу (`X_data_exclude`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 300
    },
    "executionInfo": {
     "elapsed": 139,
     "status": "ok",
     "timestamp": 1768217650943,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "i1AGN9v0w3eC",
    "outputId": "9c78af34-6e8d-4b3d-f535-51d00ac100a6"
   },
   "outputs": [],
   "source": [
    "X_data_exclude = X_data_filtered.loc[~mask_to_exclude, :]\n",
    "X_data_exclude"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Значения входных признаков, которые соответствуют оставленным классам (`X_data_include`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 1000
    },
    "executionInfo": {
     "elapsed": 109,
     "status": "ok",
     "timestamp": 1768217652781,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "81fKKw_xvyfc",
    "outputId": "c31b50ed-6c3b-4362-d8c4-65dde73b5615"
   },
   "outputs": [],
   "source": [
    "X_data_include = X_data_filtered.loc[mask_to_exclude, :]\n",
    "X_data_include"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для корректной работы с нейронной сетью модифицируем выходной признак с метками оставленных классов — значения меток выше исключённого класса уменьшим на единицу, чтобы выровнять их порядок (в дальнейшем вернём исходные). Результатом будет вектор `y_include`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 1000
    },
    "executionInfo": {
     "elapsed": 15,
     "status": "ok",
     "timestamp": 1768217655753,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "YeSoZPu1vakq",
    "outputId": "4d1b5c42-161c-4b41-8441-d4f77f16c68c"
   },
   "outputs": [],
   "source": [
    "y_include = y_multiclass[mask_to_exclude].apply(lambda y: y - 1 if y >= label_to_exclude else y)\n",
    "y_include"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "executionInfo": {
     "elapsed": 39,
     "status": "ok",
     "timestamp": 1768217660798,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "uvneeNEq2gkW"
   },
   "source": [
    "По аналогии с задачами выше:\n",
    " - с помощью функции `train_test_split` разбейте данные (`X_data_include`, `y_include`) на обучающую (`X_include_train`, `y_include_train`), валидационную (`X_include_valid`, `y_include_valid`) и тестовую выборки (`X_include_test`, `y_include_test`) с сохранением соотншений классов (сортов минеральной воды);\n",
    " - стандартизируйте или нормализуйте входные признаки и представьте их в виде тензоров;\n",
    " - из векторов с выходным признаком вычтите единицу и представьте их в виде тензоров целочисленного формата (метод `.long()`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "vXnQuhb7vaoL"
   },
   "outputs": [],
   "source": [
    "# Ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "LdbLhuA0vasw"
   },
   "source": [
    "По аналогии с задаче многоклассовой классификации создайте нейронную сеть для этих данных:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_include = nn.Sequential(\n",
    "    # Ваш код здесь\n",
    ")\n",
    "\n",
    "model_include"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "BjsyXLHgva1X"
   },
   "source": [
    "Получим логиты (длина векторов уменьшилась до 4):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 44,
     "status": "ok",
     "timestamp": 1768217668726,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "HIsQ_5MR3Jal",
    "outputId": "74349107-0245-4696-eb9d-36f5e50ceaec"
   },
   "outputs": [],
   "source": [
    "y_include_train_logits = model_include(X_include_train)\n",
    "y_include_train_logits[:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверим работу функцию потерь на этих логитах и модимцированном выходном признаке:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 51,
     "status": "ok",
     "timestamp": 1768217670810,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "2XQimt7T3Xcc",
    "outputId": "1bf129bc-aed1-433e-8083-2cea475f86ee"
   },
   "outputs": [],
   "source": [
    "criterion = nn.CrossEntropyLoss()\n",
    "criterion(y_include_train_logits, y_include_train)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "qu9a65IQva5f"
   },
   "source": [
    "По аналогии с задачей многоклассовой классификации с полным количеством классов создайте, обучите и проверьте нейронную сеть:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "8wQa5g4Bva-I"
   },
   "outputs": [],
   "source": [
    "# Ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "l4l-aUPz37ju"
   },
   "source": [
    "Поэкспериметируйте — сделайте несколько вариантов с разными архитектурами сети, оптимизаторами, значениями скорости обучения и коэффициента импульса. Выберите лучшие по качеству обучения и количеству эпох."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "executionInfo": {
     "elapsed": 9,
     "status": "ok",
     "timestamp": 1768217700545,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "k4sqBf5q41AC"
   },
   "source": [
    "Значения входных признаков, которые соответствуют исключённому классу (`X_data_exclude`) так же, как и значения входных признаков из выборок выше, стандартизируйте или нормализируйте и представьте их в виде тензоров: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_data_exclude = # Ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Пропустим обработанные признаки через обученную и проверенную нейронную сеть `model_include`, чтобы получить логиты.\n",
    "\n",
    "По данным логитам произведём классификацию (с учётом метки исключённого класса), чтобы ответить на вопрос — к каким сортам минеральной воды из оставленных соответствуют образцы исключённого класса:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 14,
     "status": "ok",
     "timestamp": 1768217702125,
     "user": {
      "displayName": "V N",
      "userId": "10751591882973173608"
     },
     "user_tz": -180
    },
    "id": "4bflOWXX5YqN",
    "outputId": "5bea95d9-0e02-455e-9163-8f14ce3664c3"
   },
   "outputs": [],
   "source": [
    "y_exclude_logits = model_include(X_data_exclude)\n",
    "\n",
    "(y_exclude_logits.argmax(dim=1) + 1).apply_(lambda y: y + 1 if y >= label_to_exclude else y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проделайте данный пункт, исключив каждый из пяти сортов. Результатом должны стать пять тензоров с восемью метками классов. Проанализируйте результаты."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Литература:\n",
    "1. Бородкин А.А., Елисеев В.Л. Основы и применение искусственных нейронных сетей. Сборник лабораторных работ: методическое пособие. – М.: Издательский дом МЭИ, 2017.\n",
    "2. MachineLearning.ru  —  профессиональный информационно-аналитический ресурс, посвященный машинному обучению, распознаванию образов и интеллектуальному анализу данных: http://www.machinelearning.ru\n",
    "3. Modern State of Artificial Intelligence  —  Online Masters program at MIPT: https://girafe.ai/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " "
   ]
  }
 ],
 "metadata": {
  "colab": {
   "authorship_tag": "ABX9TyP49vkWVMyMLjNYOlwKa9fi",
   "provenance": []
  },
  "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.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
