From db3d2c2b6a3502d482d6f05a4614b5eda69129ff Mon Sep 17 00:00:00 2001 From: NovikovVN Date: Thu, 19 Feb 2026 13:50:18 +0000 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?lab3=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab3/3.1_autoencoder.ipynb | 722 +++++++++++++++++++++++++++++++++++++ lab3/3.3_image.ipynb | 425 ++++++++++++++++++++++ lab3/avia1.bmp | Bin 0 -> 366134 bytes lab3/avia2.bmp | Bin 0 -> 366134 bytes lab3/avia3.bmp | Bin 0 -> 366134 bytes 5 files changed, 1147 insertions(+) create mode 100644 lab3/3.1_autoencoder.ipynb create mode 100644 lab3/3.3_image.ipynb create mode 100644 lab3/avia1.bmp create mode 100644 lab3/avia2.bmp create mode 100644 lab3/avia3.bmp diff --git a/lab3/3.1_autoencoder.ipynb b/lab3/3.1_autoencoder.ipynb new file mode 100644 index 0000000..5be0162 --- /dev/null +++ b/lab3/3.1_autoencoder.ipynb @@ -0,0 +1,722 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "VhB5tU1IGSIh" + }, + "source": [ + "### ЛАБОРАТОРНАЯ РАБОТА №3\n", + "## Применение многослойного персептрона. Автоассоциативная ИНС" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> Цель работы: знакомство с применением многослойного персептрона для решения задач сжатия данных, прогнозирования временных рядов и распознавания образов.\n", + ">\n", + "> Задание\n", + "> 1. Открыть файл с данными по минеральной воде, который использовался при решении задач классификации в предыдущей лабораторной работе. Построить и обучить автоассоциативные нейронные сети с 2-мя и 3-мя нейронами в скрытом слое: \n", + "> а) для исходных данных из 5-ти классов; \n", + "> б) для исходных данных из 4-х классов. \n", + "> Провести визуализацию данных в скрытом слое каждой сети на плоскость и в 3-х мерное пространство. Проанализировать полученные результаты. Выбрать и сохранить автоассоциативные ИНС, обеспечивающие наилучшее сжатие исходных данных. \n", + "> 2. … \n", + "> 3. Решить задачу распознавания 9-ти изображений самолетов. Исходные данные (файлы avia1.bmp, …, avia9.bmp) необходимо предварительно преобразовать в набор векторов со значениями признаков 0 или 1. Обученная нейронная сеть должна правильно определять модель самолета и его класс (истребитель/бомбардировщик). Принадлежность модели к определенному классу выбирается студентом самостоятельно." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Импорт библиотек:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "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 mpl_toolkits.mplot3d import Axes3D\n", + "\n", + "from torch import nn\n", + "\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Содержание: \n", + "[1. Подготовка данных](#p_1) \n", + "[2. Автоассоциативная нейронная сеть на полных данных](#p_2) \n", + "[3. Автоассоциативная нейронная сеть на неполных данных](#p_3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Подготовка данных" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Загрузим в датафрейм `data` данные о сорока образцах минеральной воды, хранящиеся в файле `min_water.txt`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 256 + }, + "id": "PkNWYbQCpsl_", + "outputId": "ee2dd8c3-2d7d-4962-bc40-a5cc2dded5db" + }, + "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": { + "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": [ + "Выпишите в список `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": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 206 + }, + "id": "d6eTUCk2Cjdc", + "outputId": "d89643dd-a410-42b2-9b91-c4534966fd1d" + }, + "outputs": [], + "source": [ + "X_data_filtered = X_data.loc[:, features]\n", + "X_data_filtered.head(n=5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Произведите нормализацию или стандартизацию (на выбор) отобранных входных данных `X_data_filtered`. Результат сохраните в переменную `X_data_preprocessed`, которую затем представьте в виде тензора `X_data_tensor`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "HclxngwAGue-" + }, + "outputs": [], + "source": [ + "# Ваш код здесь\n", + "\n", + "X_data_preprocessed = # Ваш код здесь\n", + "\n", + "X_data_tensor = # Ваш код здесь" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "AtGktiZmJiUV", + "outputId": "f9edb4f0-c5ab-4a62-af28-6552c981512f" + }, + "source": [ + "## 2. Автоассоциативная нейронная сеть на полных данных" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jsWiE7Ke1p9S" + }, + "source": [ + "Автоассоциативная сеть (или автоассоциативная память) — тип нейронной сети, способный восстанавливать полный шаблон данных по его частичному или зашумлённому представлению.\n", + "\n", + "Типичная автоассоциативная сеть содержит как минимум три скрытых слоя:\n", + " - первый скрытый слой выполняет нелинейное кодирование входных данных (энкодер);\n", + " - средний слой («узкое горло» или «бутылочное горло») формирует сжатое представление данных — в результате обучения выдаёт компактное кодирование;\n", + " - последний скрытый слой служит декодером: восстанавливает исходные данные из сжатого представления.\n", + "\n", + "Цель обучения: в процессе минимизации ошибки воспроизведения сеть стремится сделать выходной сигнал максимально близким к входному. Это эквивалентно оптимальному кодированию в «узком горле» сети." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Допишите класс `Autoencoder` структурами энкодера и декодера на основе полносвязных слоёв `nn.Linear`. В качестве функций активации используйте `nn.ReLU()`. При этом на выходе энкодера функцию активации можно не применять — это позволит сохранить отрицательные значения в кодированном представлении." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Autoencoder(nn.Module):\n", + " def __init__(self, n_inputs, n_hiddens, bottleneck_size):\n", + " super().__init__()\n", + " self.encoder = nn.Sequential(\n", + " # Ваш код здесь\n", + " )\n", + " self.decoder = nn.Sequential(\n", + " # Ваш код здесь\n", + " )\n", + "\n", + " def forward(self, x):\n", + " encoded = self.encoder(x)\n", + " decoded = self.decoder(encoded)\n", + " return decoded" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Создайте экземпляр модели с двумя нейронами в «узком горле»:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model_2 = Autoencoder(# Ващ код здесь\n", + "print(model_2)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "tGS2_TqjK_SI", + "outputId": "a1a63c33-5dd5-461e-8d20-97fc34455f21" + }, + "source": [ + "Пропустим данные через эту модель для её проверки:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Y3GS-3AaKYt7", + "outputId": "981a4cd8-3b19-4bf6-ac07-ddd13e3df131" + }, + "outputs": [], + "source": [ + "model_2(X_data_tensor[:3])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Удостоверимся, что размерность её выхода совпадает с размерностью её входа:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert X_data_tensor[:3].shape == model_2(X_data_tensor[:3]).shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Проверим, как модель обучается. Зададим оптимизатор и среднеквадратическую функцию потерь:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "VGWgT-_UKY5S" + }, + "outputs": [], + "source": [ + "optimizer = torch.optim.SGD(model_2.parameters(), lr=1.5)\n", + "criterion = nn.MSELoss()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Рассчитаем значение функции потерь:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "5kKYW89_KY8j", + "outputId": "94e90485-ca86-4204-e61d-75d247a87fc4" + }, + "outputs": [], + "source": [ + "decoded = model_2(X_data_tensor)\n", + "\n", + "loss = criterion(decoded, X_data_tensor)\n", + "loss" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Выполните несколько раз эту и предыдущую ячейку, чтобы убедиться в уменьшении ошибки:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "G_JCVLCkKZCa" + }, + "outputs": [], + "source": [ + "loss.backward()\n", + "optimizer.step()\n", + "optimizer.zero_grad()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Задайте параметры для обучения автоассоциативной сети с двумя нейронами в «узком горле»:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "torch.manual_seed(seed=42)\n", + "\n", + "model_2 = # Ваш код здесь\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()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "После обучения сети получим двумерные данные с выхода энкодера:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "HFcSAdGoOaog", + "outputId": "36330737-7606-4a6a-e532-6bf372deb34d" + }, + "outputs": [], + "source": [ + "encoded_2d = model_2.encoder(X_data_tensor).detach().numpy()\n", + "print(encoded_2d[:3])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Построим двумерную диаграмму рассеяния и отметим классы с помощью `y_multiclass`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 430 + }, + "id": "KkToPCInQQ1J", + "outputId": "244bae8a-94a9-4817-a564-69c630efea98" + }, + "outputs": [], + "source": [ + "scatter = plt.scatter(x=encoded_2d[:, 0], y=encoded_2d[:, 1], c=y_multiclass, cmap='viridis')\n", + "plt.grid(True, alpha=0.3)\n", + "\n", + "# Код для легенды\n", + "handles, labels = scatter.legend_elements(prop='colors')\n", + "plt.legend(handles, labels, loc='best', title='Classes')\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "По аналогии обучите автоассоциативную сеть с тремя нейронами в «узком горле»:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "torch.manual_seed(seed=42)\n", + "\n", + "model_3 = # Ваш код здесь\n", + "\n", + "# Ваш код здесь" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Ваш код здесь" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "После обучения сети получите трёхмерные данные с выхода энкодера:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "encoded_3d = # Ваш код здесь" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Построим трёхмерную диаграмму рассеяния:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 653 + }, + "id": "RpqVg5EeT72I", + "outputId": "2f5e070d-aa49-4eab-cd8e-277bd3a2c4b5" + }, + "outputs": [], + "source": [ + "fig = plt.figure(figsize=(10, 8))\n", + "ax = fig.add_subplot(111, projection='3d')\n", + "\n", + "scatter = ax.scatter(\n", + " xs=encoded_3d[:, 0],\n", + " ys=encoded_3d[:, 1],\n", + " zs=encoded_3d[:, 2],\n", + " c=y_multiclass,\n", + " cmap='viridis',\n", + " s=50\n", + ")\n", + "\n", + "ax.grid(True, alpha=0.3)\n", + "\n", + "handles, labels = scatter.legend_elements(prop='colors', alpha=0.8)\n", + "ax.legend(handles, labels, loc='best', title='Classes')\n", + "\n", + "# Настраиваем угол обзора:\n", + "# elev — высота, azim — азимут\n", + "ax.view_init(elev=20, azim=45)\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Сохраним в бинарные файлы `.npy` выходы энкодеров обеих моделей — для следующей лабораторной работы:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "np.save('encoded_2d.npy', encoded_2d)\n", + "np.save('encoded_3d.npy', encoded_3d)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Автоассоциативная нейронная сеть на неполных данных" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Выберите класс, который нужно исключить:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "label_to_exclude = # Ваш код здесь" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Создадим маску для исключения данных этого класса:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "WwlU6wRqXy3H" + }, + "outputs": [], + "source": [ + "mask_to_exclude = y_multiclass != label_to_exclude" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Данные исключены:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "I7sTKdWgQRWQ", + "outputId": "95d2a292-7e2f-401e-e473-7d5f09b0d7fb" + }, + "outputs": [], + "source": [ + "X_data_tensor[mask_to_exclude].shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "По аналогии с предыдущим пунктом реализуйте обучение автоассоциативных сетей с двумя и тремя нейронами в «узком горле» на неполных данных (т.е. на каждой эпохе вместо полных данных `X_data_tensor` на модель нужно подавать неполные данные `X_data_tensor[mask_to_exclude]`).\n", + "\n", + "Результаты выходов энкодеров в обоих случаях также сохраните в отдельные бинарные файлы." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Ваш код здесь" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Ваш код здесь" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "w3kJHSW3azk0" + }, + "outputs": [], + "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 +} diff --git a/lab3/3.3_image.ipynb b/lab3/3.3_image.ipynb new file mode 100644 index 0000000..3692cdc --- /dev/null +++ b/lab3/3.3_image.ipynb @@ -0,0 +1,425 @@ +{ + "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. … \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. Подготовка данных" + ] + }, + { + "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. Классификация изображений" + ] + }, + { + "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 +} diff --git a/lab3/avia1.bmp b/lab3/avia1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c69e422392d44b99a1a93790e222d62dd6478fe9 GIT binary patch literal 366134 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 Tfj9)ngE(HH6av-+zP|nsfO<9w literal 0 HcmV?d00001 diff --git a/lab3/avia2.bmp b/lab3/avia2.bmp new file mode 100644 index 0000000000000000000000000000000000000000..a3650b61e1cb15c0056151bfef256537ef01a7b7 GIT binary patch literal 366134 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 literal 0 HcmV?d00001