{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ЛАБОРАТОРНАЯ РАБОТА №3\n",
    "## Применение многослойного персептрона. Автоассоциативная ИНС"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> Цель работы: знакомство с применением многослойного персептрона для решения задач сжатия данных, прогнозирования временных рядов и распознавания образов.\n",
    ">\n",
    "> Задание\n",
    "> 1. Открыть файл с данными по минеральной воде, который использовался при решении задач классификации в предыдущей лабораторной работе. Построить и обучить автоассоциативные нейронные сети с 2-мя и 3-мя нейронами в скрытом слое:  \n",
    "> а) для исходных данных из 5-ти классов;  \n",
    "> б) для исходных данных из 4-х классов.  \n",
    "> Провести визуализацию данных в скрытом слое каждой сети на плоскость и в 3-х мерное пространство.  Проанализировать полученные результаты. Выбрать и сохранить автоассоциативные ИНС, обеспечивающие наилучшее сжатие исходных данных.  \n",
    "> 2. Исследовать возможности ИНС по прогнозированию поведения нелинейных динамических систем (построение странного аттрактора) на примере отображения Хенона. Аттрактор Хенона может быть получен из уравнений $x_{n+1} = 1 - \\alpha x_{n}^2 + y_{n}$ и $y_{n+1} = \\beta x_{n}$, где $α = 1.4$, $β = 0.3$.\n",
    "> Для прогнозирования предлагается использовать многослойный персептрон и сеть с радиально-базисными функциями.\n",
    "> Постройте также прогноз курса доллара на один день вперед. В качестве исходных данных загрузить актуальные данные с сайта центрального банка России (http://www.cbr.ru).\n",
    "> 3. Решить задачу распознавания 9-ти изображений самолетов. Исходные данные (файлы avia1.bmp, …, avia9.bmp) необходимо предварительно преобразовать в набор векторов со значениями признаков 0 или 1. Обученная нейронная сеть должна правильно определять модель самолета и его класс (истребитель/бомбардировщик). Принадлежность модели к определенному классу выбирается студентом самостоятельно."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Импорт библиотек:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "-jndqXPxywKw"
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "import torch\n",
    "import imageio\n",
    "import warnings\n",
    "\n",
    "from torch import nn\n",
    "from IPython.display import clear_output\n",
    "\n",
    "warnings.filterwarnings('ignore')\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Содержание:  \n",
    "[1. Подготовка данных](#p_1)  \n",
    "[2. Классификация изображений](#p_2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Подготовка данных<a id=\"p_1\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сохраним в список `X_data` все девять изображений. Каждое из них загрузим в виде NumPy-массива, затем преобразуем данный массив в `float32` и делением на 255 перемасштабируем в его диапазон `[0.0, 1.0]` (было `[0, 255]`). Само исходное изображение (по умолчанию цветное) преобразуем изображение в чёрно‑белое — для чего возьмём среднее по каналам R, G, B для каждого пикселя."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "Y03Y-xWo9Br1"
   },
   "outputs": [],
   "source": [
    "X_data = []\n",
    "\n",
    "for i in range(1, 10):\n",
    "    filename = f'avia{i}.bmp'\n",
    "    img = imageio.imread(filename)\n",
    "    img = img.astype('float32') / 255.\n",
    "    img = np.mean(img, axis=2)\n",
    "    X_data.append(img)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Список `y_data` содержит в себе ответ — принадлежит ли силуэт на каждом из девяти изображений бомбардировщику (`1` если принадлежит, `0` если не принадлежит):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "HVW-f4IZ6Nvi"
   },
   "outputs": [],
   "source": [
    "y_data = [0, 1, 0, 0, 1, 0, 0, 0, 1]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Выведем все девять изображений и подпишем каждое из них:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 865
    },
    "id": "luwhswXP6NzI",
    "outputId": "f6262617-d188-40f7-b38f-37deed860ab9"
   },
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(3, 3, figsize=(9, 9))\n",
    "axes = axes.ravel()\n",
    "\n",
    "for i in range(9):\n",
    "    axes[i].imshow(X_data[i], cmap='gray')\n",
    "    aircraft_type = '\\nБомбардировщик' if y_data[i] == 1 else '\\nИстребитель'\n",
    "    axes[i].set_title(aircraft_type)\n",
    "    axes[i].axis('off')\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Представим данные в виде тензоров `X_data_tensor` и `y_data_tensor`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "zyU44xbVJ9EE"
   },
   "outputs": [],
   "source": [
    "X_data_tensor = torch.tensor(X_data).float()\n",
    "y_data_tensor = torch.tensor(y_data).reshape(-1, 1).float()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверим размерность `X_data_tensor` — она показывает, что в тензоре лежат девять изображений размером 352 на 346:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "6uROCg66M2Ij",
    "outputId": "eeb259ba-c3b2-4df2-b0b8-c5c38c33c416"
   },
   "outputs": [],
   "source": [
    "X_data_tensor.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Многослойный персептрон всегда принимает на вход одномерные векторы. Однако в нашей задаче классификации силуэтов саолётов исходные изображения — как видно из размерности, двуомерные тензоры. И чтобы подать их на вход сети, их необходимо «развернуть» в одномерные векторы, сохранив количество объектов (пикселей) неизменным.\n",
    "\n",
    "Такое преобразование позволяет использовать пространственную информацию пикселей как набор признаков для полносвязных слоёв персептрона.\n",
    "\n",
    "В PyTorch это можно сделать с помощью метода `.view()` следующим образом:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Q9BNBxAmKK1Z",
    "outputId": "42786382-bf08-4f7f-ffc6-8370db68f156"
   },
   "outputs": [],
   "source": [
    "X_data_tensor.view(X_data_tensor.size(0), -1).shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для проверки перемножим длину и щирину одного из изображений:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "jOLmEcvSPeEH",
    "outputId": "f924e2ab-338f-4359-cb8a-93a3318ff675"
   },
   "outputs": [],
   "source": [
    "np.prod(X_data[0].shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Классификация изображений<a id=\"p_2\"></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Реализуйте в классе `ImageClassifier` с помощью полносвязных слоёв `nn.Linear` многослойный персептрон. В качестве промежуточных функций активации используйте `nn.ReLU()`, а в качестве финальной — сигмоиду, поскольку решается задача бинарной классификации."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ImageClassifier(nn.Module):\n",
    "    def __init__(self, input_size):\n",
    "        super().__init__()\n",
    "        self.seq = nn.Sequential(\n",
    "            # Ваш код здесь\n",
    "        )\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = x.view(x.size(0), -1)\n",
    "        return self.seq(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Создайте экземпляр модели:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = ImageClassifier(input_size=# Ваш код здесь\n",
    "\n",
    "model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проверим, что модель при приходе данных возвращает вероятности:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "x3QUCgUnOy_Z",
    "outputId": "be3f4404-b3d5-4bf3-b510-8d7b903e5fc8"
   },
   "outputs": [],
   "source": [
    "model(X_data_tensor)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Метрика accuracy:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "WMSGULyhQVNA"
   },
   "outputs": [],
   "source": [
    "def accuracy(y_pred, y_true):\n",
    "    return torch.sum(y_pred == y_true) / len(y_true)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Задайте параметры для обучения нейронной сети:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "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": {},
   "outputs": [],
   "source": [
    "loss_history = []\n",
    "\n",
    "for epoch in range(epochs):\n",
    "    # Ваш код здесь\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_history[-1]:.6f}')\n",
    "        plt.grid(True, alpha=0.3)\n",
    "        plt.legend(loc='best')\n",
    "        plt.show()\n",
    "\n",
    "        print(f'Accuracy: {accuracy((y_prob > 0.5).float(), y_data_tensor).item():.3f}')"
   ]
  },
  {
   "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
}
