{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ЛАБОРАТОРНАЯ РАБОТА №1.2\n",
    "## ИЗУЧЕНИЕ ОСНОВНЫХ ПОНЯТИЙ ТЕОРИИ ИСКУССТВЕННЫХ НЕЙРОННЫХ СЕТЕЙ"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> Цель работы: изучение основных понятий теории искусственных нейронных сетей на примере простых задач распознавания логических функций («И», «ИЛИ», «исключающее ИЛИ»)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Импорт библиотек:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "bvIkPEqTXcpG"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import torch\n",
    "import matplotlib.pyplot as plt\n",
    "from IPython.display import clear_output\n",
    "\n",
    "from torch import nn\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В заключительной части данной лабораторной работы мы применяем PyTorch — современный фреймворк для научных вычислений и глубокого обучения в Python.\n",
    "\n",
    "PyTorch во многом напоминает NumPy по принципам работы и синтаксису:\n",
    "- оперирует многомерными массивами (в PyTorch они называются *тензорами*, или *Tensors*), которые являются прямым аналогом `ndarray` из NumPy;\n",
    "- поддерживает похожий синтаксис для базовых операций: индексация, срезы, арифметические действия;\n",
    "- реализует векторизованные вычисления и механизм broadcasting (широковещательное сложение);\n",
    "- позволяет выполнять стандартные математические операции с интуитивно понятными методами (например, `tensor.mean()` в PyTorch аналогичен `array.mean()` в NumPy).\n",
    "\n",
    "\n",
    "Некоторые основные компоненты PyTorch:\n",
    "\n",
    "1. Тензоры (Tensors)  \n",
    "   - многомерные массивы с поддержкой вычислений на GPU;\n",
    "   - аналог `numpy.ndarray`, но с расширенными возможностями для машинного обучения.\n",
    "\n",
    "\n",
    "2. Автоматическое дифференцирование (Autograd)  \n",
    "   - механизм для автоматического вычисления градиентов;\n",
    "   - критически важен для обучения нейронных сетей (реализует алгоритм обратного распространения ошибки). \n",
    "   \n",
    "\n",
    "3. Модули (nn.Module)  \n",
    "   - базовые строительные блоки для создания нейронных сетей;\n",
    "   - включают слои, функции активации, функции потерь и др.\n",
    "\n",
    "\n",
    "4. Оптимизаторы (optim)  \n",
    "   - реализации популярных алгоритмов оптимизации: SGD, Adam, RMSprop и др.;\n",
    "   - упрощают обновление параметров модели.\n",
    "\n",
    "\n",
    "Ключевые возможности PyTorch:\n",
    "\n",
    "- Создание и обучение нейронных сетей любой сложности — от простых перцептронов до трансформеров.\n",
    "- Вычисления на CPU и GPU с минимальным изменением кода (достаточно переместить тензоры на устройство `cuda`).\n",
    "- Динамические вычислительные графы — в отличие от статических графов в некоторых других фреймворках, PyTorch строит граф операций «на лету», что упрощает отладку и эксперименты.\n",
    "- Гибкость в проектировании архитектур — легко создавать кастомные слои и модели, переопределять методы обратного прохода.\n",
    "\n",
    "\n",
    "Преимущества для исследований и разработки:\n",
    "\n",
    "- Интуитивный интерфейс, близкий к NumPy — низкий порог входа для тех, кто знаком с NumPy.\n",
    "- Гибкая система наследования — возможность создавать собственные модули и слои.\n",
    "- Обширное сообщество — множество туториалов, примеров, библиотек и форумов.\n",
    "- Совместимость с другими библиотеками — лёгкая интеграция с NumPy, SciPy, Matplotlib и др.\n",
    "- Производительность — ускорение вычислений на GPU (в 10–100 раз по сравнению с CPU для крупных задач).\n",
    "\n",
    "\n",
    "Официальная документация: https://pytorch.org/docs/stable/index.html"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В качестве примера вышесказанного переписанная на PyTorch метрика accuracy: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "l4lzUxFY0ej5"
   },
   "outputs": [],
   "source": [
    "def accuracy(y_pred, y_true):\n",
    "    return torch.sum(y_pred == y_true) / len(y_true)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Содержание:  \n",
    "[1. Решение задачи логического «И»](#p_1)  \n",
    "[2. Решение задачи логического «ИЛИ»](#p_2)  \n",
    "[3. Решение задачи логического «исключающего ИЛИ»](#p_3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Решение задачи логического «И»<a id=\"p_1\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Представим входные данные для решения задачи логического «И» в виде тензора `X_data`.\n",
    "\n",
    "Операции в нейронных сетях (умножение весов, активация, вычисление градиентов) выполняются только с вещественными числами, поэтому через `.float()` приводим тензор к вещественному типу данных — иначе далее возможна ошибка типа данных."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Ej0DmW9uXlG0",
    "outputId": "b3821094-83d4-40f1-faa6-7773535b9ea6"
   },
   "outputs": [],
   "source": [
    "X_data = torch.tensor([[0, 0], [0, 1], [1, 0], [1, 1]]).float()\n",
    "print(X_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Аналогичная операция с выходным вектором:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "khRrzE2fXlR4",
    "outputId": "a1aa980e-fa15-4c7c-ba68-18f0f5588f7f"
   },
   "outputs": [],
   "source": [
    "y_and_data = torch.tensor([0, 0, 0, 1]).reshape(-1, 1).float()\n",
    "print(y_and_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Создание нейронной сети в PyTorch: последовательная модель**\n",
    "\n",
    "Один из самых простых и наглядных способов создать нейронную сеть в PyTorch — использовать `nn.Sequential`. Это контейнер, который организует слои в последовательный пайплайн: данные проходят через каждый слой по порядку, от входа к выходу — выход предыдущего слоя автоматически становится входом следующего.\n",
    "\n",
    "Создадим данным способом нейронную сеть `model_seq` с одним слоем нейронов.\n",
    "\n",
    "Пояснения к выбранным слоям в её архитектуре:\n",
    "  1. `nn.Linear(in_features=2, out_features=1)` — полностью соединённый (плотный, полносвязный) слой.  \n",
    "   \n",
    "   Параметры:\n",
    "   - `in_features=2` — размерность входного вектора (2 признака, соответствующие входам логической операции);\n",
    "   - `out_features=1` — размерность выхода (1 нейрон для итогового результата).\n",
    "   \n",
    "   Математическая операция:  \n",
    "   $$\n",
    "   y = W \\cdot X + b,\n",
    "   $$  \n",
    "\n",
    "  2. `nn.Sigmoid()` — функция активации (сигмоида).\n",
    "  \n",
    "После создания экземпляр `model_seq` можно:\n",
    "- вызывать как функцию: `model_seq(X_data)` — прямой проход через сеть;\n",
    "- получать параметры: `model_seq.parameters()` — для оптимизации;\n",
    "- выводить структуру: печать `model_seq` покажет последовательность слоёв."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "gqr4pECOoDpG",
    "outputId": "145244ca-2ede-498b-8a01-9841f6df041e"
   },
   "outputs": [],
   "source": [
    "model_seq = nn.Sequential(\n",
    "    nn.Linear(in_features=2, out_features=1),\n",
    "    nn.Sigmoid()\n",
    ")\n",
    "\n",
    "model_seq"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Переберём все обучаемые параметры сети `model_seq` — результатом будет список тензоров, содержащих исходные веса и смещения сети:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "32TRT1kv9_Ln",
    "outputId": "49e48ed3-3909-46d6-ac66-c0b59d1b9a5b"
   },
   "outputs": [],
   "source": [
    "[x for x in model_seq.parameters()]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Обратите внимание в выводе на флаг `requires_grad=True`.\n",
    "\n",
    "Он указывает, что для этого тензора в процессе обратного распространения ошибки нужно будет вычислять градиенты (производные) всех математических операций, через которые далее в коде этот тензор проходит. Без этого флага параметры не будут обновляться при обучении. Флаг при задании тензора также можно переключать на `False`.\n",
    "\n",
    "Как это работает:\n",
    "\n",
    " 1. Во время прямого прохода (`forward`) PyTorch строит вычислительный граф, запоминая операции с тензорами, у которых `requires_grad=True`.\n",
    " 2. При вызове `loss.backward()` автоматически вычисляются градиенты по всем параметрам с `requires_grad=True`, при этом значение каждое градиенты сохраняются в атрибуте `.grad` тензора.\n",
    " 3. Оптимизатор (объект, реализующий алгоритм обновления параметров модели на основе вычисленных градиентов — например, SGD) использует эти градиенты для обновления параметров."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Рассмотрим всё на примере.\n",
    "\n",
    "Выберем в качестве оптимизатора параметров `model_seq` SGD (с достаточно большим шагом обучения `lr` для наглядности) и среднеквадратичную функцию потерь:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "DMJYrTlE-VqS"
   },
   "outputs": [],
   "source": [
    "optimizer = torch.optim.SGD(model_seq.parameters(), lr=1.5)\n",
    "criterion = nn.MSELoss()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a id=\"loop_and\"></a>Пропустим входные данные `X_data` через `model_seq`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "5xiqGW-opJNK",
    "outputId": "44183227-fe44-45f7-fb95-3dff575d702e"
   },
   "outputs": [],
   "source": [
    "z = model_seq(X_data)\n",
    "z"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Атрибут `grad_fn=<SigmoidBackward0>` в выводе обозначает:\n",
    "- Тензор `z` — результат работы сигмоиды в последнем слое сети;\n",
    "- PyTorch «запомнил» эту операцию в своём вычислительном графе;\n",
    "- При обратном распространении ошибки градиенты будут корректно рассчитаны по формуле производной сигмоиды и переданы дальше в сеть."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Рассчитаем значение функции потерь:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "kn_XCMP8-fVm",
    "outputId": "f5813ee6-a1f3-4b34-acee-77978ecf7b8f"
   },
   "outputs": [],
   "source": [
    "loss = criterion(z, y_and_data)\n",
    "loss"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В атрибуте `grad_fn=<SigmoidBackward0>` аналогично видно, что в вычислительном графе к тензорам применена функция среднеквадратичной ошибки.\n",
    "\n",
    "Вычислим градиенты функции потерь по всем параметрам модели согласно вычислительному графу:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "kWc5ip2O-wKp"
   },
   "outputs": [],
   "source": [
    "loss.backward()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Обновим с помошью оптимизатора все параметры сети: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "dxh8RIJV-1Hy"
   },
   "outputs": [],
   "source": [
    "optimizer.step()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим значения обновлённых параметров:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "AQt5IglN_BJx",
    "outputId": "b916e29e-874d-4396-a447-3556d5b5757e"
   },
   "outputs": [],
   "source": [
    "[x for x in model_seq.parameters()]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Метод `optimizer.zero_grad()` вызывается обязательно перед каждым последующим обратным проходом (`loss.backward()`) в цикле обучения для **обнуления градиентов всех параметров** модели, управляемых этим оптимизатором.\n",
    "\n",
    "Почему это необходимо? \n",
    "\n",
    "В PyTorch градиенты по умолчанию накапливаются (суммируются) при каждом вызове `loss.backward()`. Если не обнулять градиенты:\n",
    "- значения градиентов будут расти с каждой итерацией;\n",
    "- веса модели начнут обновляться некорректно (с учётом «старых» градиентов);\n",
    "- возможен эффект «взрывных градиентов» (gradients explode), когда значения становятся настолько большими, что превращаются в `NaN` (не число)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "KRD01eVD_yjE"
   },
   "outputs": [],
   "source": [
    "optimizer.zero_grad()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сделайте несколько проходов (итераций) от [первого шага обратного распространения ошибки](#loop_and) до данной ячейки. Убедитесь, что ошибка сети `loss` понижается."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Рассмотрим ещё один способ создания сети в PyTorch.\n",
    "\n",
    "**Создание нейронной сети в PyTorch: через ООП**\n",
    "\n",
    "В PyTorch принято определять архитектуры нейронных сетей как классы, наследующие от `nn.Module`. Это позволяет:\n",
    "- структурировать код в виде повторно используемых компонентов;\n",
    "- чётко разделять инициализацию параметров (`__init__`) и прямой проход (`forward`);\n",
    "- легко расширять и модифицировать модели;\n",
    "- использовать встроенные механизмы PyTorch (оптимизаторы, сохранение/загрузка моделей).\n",
    "\n",
    "Пример сети с одним полносвязным слоем и сигмоидой в качестве функции активации:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "PF5yli7WXlVd"
   },
   "outputs": [],
   "source": [
    "class OneLayerNetwork(nn.Module):\n",
    "    def __init__(self, n_inputs=2, n_outputs=1):\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            nn.Linear(in_features=n_inputs, out_features=n_outputs),\n",
    "            nn.Sigmoid()\n",
    "        )\n",
    "\n",
    "    def forward(self, X):\n",
    "        return self.model(X)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сейчас все слои для удобства по-прежнему собраны в `nn.Sequential`. Чтобы повысить гибкость модели, в методе `__init__` можно по отдельности задать все необходимые слои, а в `forward` — определять порядок их применения. Такой способ даёт больше контроля над архитектурой (пример разберём в одной из следующих лабораторных работ).\n",
    "\n",
    "Сигмоиду здесь можно также заменить на `nn.ReLU` или другую функцию активации — но для отделения от экземплятор с исходной архитектурой создайте другой класс (например, `OneLayerReLUNetwork()`)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Создадим экземпляр однослойной сети этого класса для решения задачи логического «И»:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "kIuQLzUIwVwC",
    "outputId": "91aa25b7-aae4-4655-e83b-4abac88531c8"
   },
   "outputs": [],
   "source": [
    "model_one_layer = OneLayerNetwork()\n",
    "model_one_layer"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По аналогии с прошлым ноутбуком зададим \n",
    " - данные для обучения (входные и выходные);\n",
    " - экземпляр нейронной сети (через последовательность `nn.Sequential()` или класс `OneLayerNetwork()`);\n",
    " - количество эпох или итераций в процессе обучения (желательно больше 5);\n",
    " - скорость обучение и коэффициент импульса;\n",
    " - оптимизатор (попробуйте разные алгоритмы — `torch.optim.SGD()`, `torch.optim.Adam()` и другие) (значение `learning_rate` передаётся в `lr`) (учтите, что в `torch.optim.Adam()` нет импульса);\n",
    " - функцию потерь (можно оставить `nn.MSELoss()`, однако поскольку решается задача бинарной классификации, можно выбрать бинарную кросс-энтропию `nn.BCELoss()`).\n",
    " \n",
    " \n",
    "*Примечание*. Вы можете использовать для обучения сети либо код ниже, либо модифицированный на его основе — например, реализовать функцию обучения нейронной сети и проверки её работы."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "p09jzhOY3MyQ"
   },
   "outputs": [],
   "source": [
    "y_true = y_and_data\n",
    "\n",
    "# Перебор seed для инициализации параметров\n",
    "torch.manual_seed(seed=42)\n",
    "\n",
    "model = # Ваш код здесь\n",
    "\n",
    "epochs = # Ваш код здесь\n",
    "\n",
    "learning_rate = # Ваш код здесь\n",
    "momentum = # Ваш код здесь\n",
    "\n",
    "optimizer = # Ваш код здесь\n",
    "criterion = # Ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Обучение нейронной сети для решения задачи логического «И»:**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 556
    },
    "id": "qKimvoStsZXv",
    "outputId": "725ec1a2-2d68-4f24-91c6-3469a39efbc4"
   },
   "outputs": [],
   "source": [
    "loss_history = []\n",
    "\n",
    "for epoch in range(epochs):\n",
    "    \n",
    "    # Метод .train() переводит модель в режим обучения\n",
    "    model.train()\n",
    "\n",
    "    optimizer.zero_grad()\n",
    "\n",
    "    z = model(X_data)\n",
    "    model_answer_interpretation = (z >= 0.5).float()\n",
    "\n",
    "    loss = criterion(z, y_true)\n",
    "    \n",
    "    # Метод .item() извлекает скалярное значение из тензора loss\n",
    "    loss_history.append(loss.item())\n",
    "\n",
    "    loss.backward()\n",
    "\n",
    "    optimizer.step()\n",
    "    \n",
    "    # Метод .eval() переводит модель в режим валидации/тестирования (об этом в следующей лабораторной)\n",
    "    model.eval()\n",
    "\n",
    "    if (epoch + 1) % 5 == 0:\n",
    "\n",
    "        clear_output(True)\n",
    "        plt.plot(range(1, epoch+2), loss_history, label='Loss')\n",
    "        plt.title(f'Epoch: {epoch + 1}, Loss: {loss:.6f}')\n",
    "        plt.grid(True, alpha=0.3)\n",
    "        plt.legend(loc='best')\n",
    "        plt.show()\n",
    "\n",
    "        print('Test:')\n",
    "        for i in range(len(X_data)):\n",
    "            # Метод .detach() создаёт копию тензора, отсоединённую от вычислительного графа,\n",
    "            # чтобы избежать ненужного вычисления градиентов для исходного тензора\n",
    "            print(f'Input: {X_data.numpy()[i]}, Output: {torch.round(z[i].detach() * 100).numpy() / 100}, Prediction: {model_answer_interpretation[i].numpy()}, True: {y_true[i].numpy()}')\n",
    "\n",
    "        print(f'Accuracy: {accuracy(model_answer_interpretation, y_true):.2f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверим качество обучения сети:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert accuracy(model_answer_interpretation, y_and_data) == 1\n",
    "assert np.mean(loss_history[:5]) > np.mean(loss_history[-5:])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Поэкспериметируйте с параметрами обучения — сделайте несколько вариантов с разными функциями потерь, оптимизаторами, значениями скорости обучения и коэффициента импульса. Выберите лучшие по качеству обучения и количеству эпох."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Решение задачи логического «ИЛИ»<a id=\"p_2\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По аналогии с задачей логического «И» решите задачу логического «ИЛИ».\n",
    "\n",
    "Выходные данные:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "UGFSsTGh4Xyp",
    "outputId": "40e554c0-51a0-4adb-ca8b-8e46c83c2e7d"
   },
   "outputs": [],
   "source": [
    "y_or_data = torch.tensor([0, 1, 1, 1]).reshape(-1, 1).float()\n",
    "print(y_or_data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Ваш код здесь"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверьте качество обучения сети:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert accuracy(model_answer_interpretation, y_or_data) == 1\n",
    "assert np.mean(loss_history[:5]) > np.mean(loss_history[-5:])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Поэкспериметируйте с параметрами обучения — сделайте несколько вариантов с разными функциями потерь, оптимизаторами, значениями скорости обучения и коэффициента импульса. Выберите лучшие по качеству обучения и количеству эпох."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Решение задачи логического «исключающего ИЛИ»<a id=\"p_3\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Выходные данные:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "gNigOR9N5X3z",
    "outputId": "297e81ad-0eb7-403a-8dbf-0df7706c934e"
   },
   "outputs": [],
   "source": [
    "y_xor_data = torch.tensor([0, 1, 1, 0]).reshape(-1, 1).float()\n",
    "print(y_xor_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "6hL-mvyF5T58"
   },
   "source": [
    "Напомним, что однослойной сетью задачу «исключающего ИЛИ» решить нельзя. В нейронную сеть требуется добавить ещё один слой с как минимум двумя нейронами.\n",
    "\n",
    "Модифицируем для этого класс `OneLayerNetwork()` в класс `TwoLayersNetwork()` — в последовательсти в `self.model` добавим ещё один полносвязный слой с сигмоидой для активации. Добавлен также параметр `n_hiddens` — количество нейронов в скрытом слое. Обратите внимание на задание размерности в последовательности слоёв."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "TGcgUAFQtXn7"
   },
   "outputs": [],
   "source": [
    "class TwoLayersNetwork(nn.Module):\n",
    "    def __init__(self, n_inputs=2, n_hiddens=2, n_outputs=1):\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            nn.Linear(in_features=n_inputs, out_features=n_hiddens),\n",
    "            nn.Sigmoid(),\n",
    "            nn.Linear(in_features=n_hiddens, out_features=n_outputs),\n",
    "            nn.Sigmoid()\n",
    "        )\n",
    "\n",
    "    def forward(self, X):\n",
    "        return self.model(X)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Создадим экземпляр двухслойной сети этого класса для решения задачи «исключающего ИЛИ»:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "afK0vsG1t2oL",
    "outputId": "bab9683b-fefe-4734-f948-340a353b99c8"
   },
   "outputs": [],
   "source": [
    "model_two_layers = TwoLayersNetwork()\n",
    "model_two_layers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По аналогии с задачами логического «И» и логического «ИЛИ» решите «задачу исключающего ИЛИ»:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Ваш код здесь\n",
    "\n",
    "# Вариант задания сети с разным количество нейронов в скрытом слое\n",
    "n_hiddens = 2\n",
    "model = TwoLayersNetwork(n_hiddens=n_hiddens)\n",
    "\n",
    "# Ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверьте качество обучения сети:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert accuracy(model_answer_interpretation, y_xor_data) == 1\n",
    "assert np.mean(loss_history[:5]) > np.mean(loss_history[-5:])"
   ]
  },
  {
   "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": {
   "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
}
