{ "cells": [ { "cell_type": "markdown", "id": "e41aceab", "metadata": { "id": "2PQW7OXEoWEZ" }, "source": [ "# Введение в анализ данных\n", "## Домашнее задание 4, часть 1. Нейронные сети." ] }, { "cell_type": "markdown", "id": "c8f6d08c", "metadata": { "id": "aRt8VO2EoWEa" }, "source": [ "**Правила, прочитайте внимательно:**\n", "\n", "* Выполненную работу нужно отправить телеграм-боту `@thetahat_ds25_bot`. Для начала работы с ботом каждый раз отправляйте `/start`. Дождитесь подтверждения от бота, что он принял файл. Если подтверждения нет, то что-то не так. **Работы, присланные иным способом, не принимаются.**\n", "* Дедлайн см. в боте. После дедлайна работы не принимаются кроме случаев наличия уважительной причины.\n", "* Прислать нужно **ноутбук в формате `ipynb`**. Если вы строите интерактивные графики, их стоит прислать в формате html.\n", "* Следите за размером файлов. **Бот не может принимать файлы весом более 20 Мб.** Если файл получается больше, заранее разделите его на несколько.\n", "* Выполнять задание необходимо полностью самостоятельно. **При обнаружении списывания всем участникам списывания дается штраф -2 балла к итоговой оценке за семестр.**\n", "* Решения, размещенные на каких-либо интернет-ресурсах, не принимаются. Кроме того, публикация решения в открытом доступе может быть приравнена к предоставлении возможности списать.\n", "* Обратите внимание на правила использования ИИ-инструментов при решении домашнего задания.\n", "* **Код из рассказанных на занятиях ноутбуков** можно использовать без ограничений.\n", "* Для выполнения задания используйте этот ноутбук в качестве основы, ничего не удаляя из него. Можно добавлять необходимое количество ячеек.\n", "* Комментарии к решению пишите в markdown-ячейках.\n", "* Выполнение задания (ход решения, выводы и пр.) должно быть осуществлено на русском языке.\n", "* Решение проверяется системой ИИ-проверки **ThetaGrader**. Результат проверки валидируется и исправляется человеком, после чего комментарии отправляются студентам.\n", "* Если код будет не понятен проверяющему, оценка может быть снижена.\n", "* Никакой код из данного задания при проверке запускаться не будет. *Если код студента не выполнен, недописан и т.д., то он не оценивается.*\n", "* **Код из рассказанных на занятиях ноутбуков** можно использовать без ограничений.\n", "\n", "\n", "Важно!!! Правила заполнения ноутбука:\n", "* Запрещается удалять имеющиеся в ноутбуке ячейки, менять местами положения задач.\n", "* Сохраняйте естественный линейный порядок повествования в ноутбуке сверху-вниз.\n", "* Отвечайте на вопросы, а также добавляйте новые ячейки в предложенных местах, которые обозначены `<...>`.\n", "* В markdown-ячейка, содержащих описание задачи, находятся специальные отметки, которые запрещается модифицировать.\n", "* При нарушении данных правил работа может получить 0 баллов.\n", "\n", "\n", "**Баллы за задание:**\n", "\n", "Легкая часть (достаточно на \"хор\"):\n", "\n", "* Задача 1 — 60 баллов\n", "* **Остальные задачи будут выложены в части 2 с отдельным дедлайном.**\n", "\n", "Баллы учитываются в обязательной части курса и не влияют на оценку по факультативной части." ] }, { "cell_type": "code", "execution_count": null, "id": "504cd593", "metadata": { "id": "TLUn-L0HoWEb" }, "outputs": [], "source": [ "# Bot check\n", "\n", "# HW_ID: fpmi_ad4_part1\n", "# Бот проверит этот ID и предупредит, если случайно сдать что-то не то.\n", "\n", "# Status: not final\n", "# Перед отправкой в финальном решении удали \"not\" в строчке выше.\n", "# Так бот проверит, что ты отправляешь финальную версию, а не промежуточную.\n", "# Никакие значения в этой ячейке не влияют на факт сдачи работы." ] }, { "cell_type": "code", "execution_count": null, "id": "1e8aa725", "metadata": {}, "outputs": [], "source": [ "from typing import Tuple\n", "import numpy as np\n", "import torch\n", "from torchvision import transforms\n", "from torchvision.datasets import MNIST\n", "\n", "# Допишите сюда необходимые импорты" ] }, { "cell_type": "markdown", "id": "58a2d143", "metadata": { "id": "N5pKs47noWEc" }, "source": [ "---\n", "### Легкая часть" ] }, { "cell_type": "markdown", "id": "23ebacac", "metadata": {}, "source": [ "\n", "\n", "---\n", "### Задача 1.\n", "\n", "**При решении данной задачи можно использовать ИИ-инструменты только для построения графиков и оформления документаций к коду.**" ] }, { "cell_type": "markdown", "id": "3766d31c", "metadata": {}, "source": [ "Обратимся вновь к уже знакомому нам по первому занятию датасету **MNIST**. Как вы помните, этот набор данных содержит изображения рукописных цифр, каждое из которых имеет размер **28x28**. Всего в датасете содержится **60 000** изображений в обучающей выборке и еще **10 000** — в тестовой.\n", "\n", "На первом занятии мы работали с сокращенной версией этого датасета, содержащей **1797** изображений размером **8x8**.\n", "\n", "В этой домашней работе мы выберем более \"сложный\" вариант, а именно:\n", "\n", "* вернемся к исходной версии датасета с изображениями размером **28x28**,\n", "* сожмем изображения в два раза по каждой из размерностей, то есть до **14x14**,\n", "* оставим **10 000** изображений в обучающей выборке.\n", "\n", "> *Примечание.* При желании вы можете усложнить задачу — отказаться от сжатия изображений и/или увеличить количество данных. Однако учтите, что в таком случае модели будут работать дольше, и, возможно, вам будет сложнее достичь желаемых результатов." ] }, { "cell_type": "markdown", "id": "7ca2dd9b", "metadata": {}, "source": [ "**Наша задача остается прежней:** *по изображению определить, какая цифра на нем нарисована.*\n", "На этот раз мы будем решать ее с помощью простых нейронных сетей. Также мы заглянем \"под капот\" нейросетей, чтобы лучше понять процесс их обучения.\n", "\n", "> *Примечание.* Обычно подобные задачи, связанные с изображениями, решаются с помощью сверточных нейронных сетей, которые лучше подходят для работы с изображениями. Они станут темой нашего следующего занятия, ждем всех!" ] }, { "cell_type": "markdown", "id": "8e0bea5e", "metadata": {}, "source": [ "Ниже представлена готовая функция для загрузки и предварительной обработки данных.\n", "В рамках этой функции мы выполняем следующие шаги.\n", "* Загрузка исходных данных.\n", "* Сохранение **10 000** изображений в качестве обучающей выборки.\n", "* Сжатие изображений в два раза по каждой из осей, что приводит к размеру **14x14**..\n", "* Растягиваем изображение размером **14x14** в вектор длиной $14 \\cdot 14 = 196$. Более подробно эта операция описана в первой лекции." ] }, { "cell_type": "code", "execution_count": null, "id": "22295a1d", "metadata": {}, "outputs": [], "source": [ "def load_mnist(\n", " train_size: int = 6000, target_size: int = 14\n", ") -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:\n", " \"\"\"Загружает и подготавливает данные MNIST для обучения и тестирования.\n", "\n", " Параметры:\n", " train_size (int): Количество обучаемых данных. По умолчанию 6000.\n", " target_size (int): Размер, до которого уменьшаются изображения. По умолчанию 14.\n", "\n", " Возвращает:\n", " train_images (torch.Tensor): Тензор с изображениями для обучения.\n", " train_labels (torch.Tensor): Тензор с метками для обучения.\n", " test_images (torch.Tensor): Тензор с изображениями для тестирования.\n", " test_labels (torch.Tensor): Тензор с метками для тестирования.\n", "\n", " Данные загружаются из набора MNIST, нормализуются, уменьшаются в\n", " размере и преобразуются в векторы. Для обучения используется только\n", " часть (train_fraction) от исходного набора данных.\n", " \"\"\"\n", "\n", " PIXEL_MAX_VALUE = 255.0 # Для нормализации значений пикселей\n", "\n", " # Создаем преобразование для данных: конвертируем изображения в тензоры PyTorch\n", " transform = transforms.Compose(\n", " [\n", " # Преобразует изображение в тензор и нормализует значения в диапазоне [0, 1]\n", " transforms.ToTensor(),\n", " ]\n", " )\n", "\n", " # Загружаем обучающий и тестовый наборы данных MNIST\n", " train_dataset = MNIST(root=\"./data\", train=True, transform=transform, download=True)\n", " test_dataset = MNIST(root=\"./data\", train=False, transform=transform, download=True)\n", "\n", " # Выбираем часть обучающего набора данных\n", " train_indices = np.random.choice(len(train_dataset), train_size, replace=False)\n", "\n", " # Преобразуем изображения в тензоры и нормализуем их, деля на PIXEL_MAX_VALUE\n", " train_images = train_dataset.data[train_indices].unsqueeze(1).float() / PIXEL_MAX_VALUE\n", " test_images = test_dataset.data.unsqueeze(1).float() / PIXEL_MAX_VALUE\n", "\n", " # Уменьшаем размер изображений до target_size x target_size с использованием билинейной интерполяции\n", " train_images = torch.nn.functional.interpolate(\n", " train_images, size=(target_size, target_size), mode=\"bilinear\", align_corners=False\n", " )\n", " test_images = torch.nn.functional.interpolate(\n", " test_images, size=(target_size, target_size), mode=\"bilinear\", align_corners=False\n", " )\n", "\n", " # Преобразуем изображения в векторы (разворачиваем в одномерные массивы)\n", " train_images = train_images.view(train_size, -1)\n", " test_images = test_images.view(len(test_dataset), -1)\n", "\n", " # Получаем метки для выбранных обучающих и всех тестовых данных\n", " train_labels = train_dataset.targets[train_indices]\n", " test_labels = test_dataset.targets\n", "\n", " return train_images, train_labels, test_images, test_labels" ] }, { "cell_type": "markdown", "id": "01e2ff3d", "metadata": {}, "source": [ "Загрузим данные и посмотрим на их размерности" ] }, { "cell_type": "code", "execution_count": null, "id": "33fa1ac4", "metadata": {}, "outputs": [], "source": [ "train_images, train_labels, test_images, test_labels = load_mnist()\n", "\n", "print(\"Train:\", train_images.shape, train_labels.shape)\n", "print(\"Test:\", test_images.shape, test_labels.shape)" ] }, { "cell_type": "markdown", "id": "58a20ab4", "metadata": {}, "source": [ "\n", "\n", "**1.** Используя `Sequential` подход в PyTorch, напишите модель нейронной сети по следующему описанию.\n", "\n", "* **Вход**: изображение в виде вектора.\n", "* **Два линейных слоя** с промежуточной размерностью 64.\n", "* **Функция активации**: между линейными слоями используется `ReLU`, а на выходе второго слоя — отсутствует (или используется тождественная функция).\n", "* **Выход**: вектор логитов размером 10, соответствующий количеству классов.\n", "\n", "> Эта нейронная сеть для изображения $x$ оценивает вектор логитов $\\left(\\ell_0(x),..., \\ell_9(x)\\right)$ принадлежности к каждому из классов, аналогично логистической регрессии. Имея оценку логитов $\\left(\\ell_0(x),..., \\ell_9(x)\\right)$ , можно получить оценку вероятности $p_k$ для каждого класса по следующей формуле, используя softmax-функцию (обобщение логистической сигмоиды)\n", "> $$\n", "\\widehat{p}_k(x) = \\frac{\\exp\\left(\\widehat{\\ell}(x)\\right)}{\\exp\\left(\\widehat{\\ell}_0(x)\\right) +... + \\exp\\left(\\widehat{\\ell}_9(x)\\right)},\n", "$$\n", "> которая реализуется с помощью функции `nn.functional.softmax(..., dim=-1)`. В качестве оценки класса можно взять класс с наибольшей вероятностью, что можно реализовать с помощью метода `argmax(axis=...)` у тензора в PyTorch." ] }, { "cell_type": "code", "execution_count": null, "id": "89b571f0", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "f0078531", "metadata": {}, "source": [ "\n", "\n", "**2.** **Напишите цикл обучения нейросети**, используя кросс-энтропию `nn.CrossEntropyLoss()` качестве лосс-функции. Это обобщение бинарной кросс-энтропии, которую мы рассматривали на лекции по логистической регрессии. Ее реализация в PyTorch принимает на вход логиты (что и возвращает наша нейросеть) и истинные метки классов. Обучайте сеть на полном наборе данных с помощью метода градиентного спуска `torch.optim.SGD`, не разбивая данные на случайные батчи (как это делается в SGD).\n", "\n", "\n", "Примерно каждые 5-10 итераций выполняйте следующие действия:\n", "\n", "* Получите текущие предсказания классов для обучающей и тестовой выборок.\n", "* Посчитайте точность классификации для этих выборок.\n", "* Постройте график зависимости точности классификации от номера итерации. Перед построением графика используйте `clear_output(wait=True)` для плавной очистки холста.\n", "* Сохраните следующие значения:\n", " * Значение лосс-функции.\n", " * Точность классификации для обучающей и тестовой выборок.\n", " * Матрицы весов для каждого слоя.\n", "\n", "*Замечания:*\n", "* Рекомендуем сохранять данные в заранее подготовленный словарь, ключи которого соответствуют именам переменных (например, `\"weights_layer_2\"`), а значения словаря представляют собой списки значений этих переменных по итерациям.\n", "* При сохранении не забывайте вызывать метод `.detach().numpy()` для преобразования матриц в массивы numpy. Также может потребоваться заново инициализировать матрицы с помощью `np.array(...)`, чтобы сохранить сами матрицы, а не ссылки на них, по которым значения меняются в процессе обучения сети.\n", "\n", "Выполните 10 000 итераций обучения. Сколько времени это заняло?" ] }, { "cell_type": "code", "execution_count": null, "id": "c234fd25", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "5a1cbec2", "metadata": {}, "source": [ "Проверьте себя, точность классификации на тестовой выборке должна быть около $90\\%$. Если вы получили значительно меньший результат (менее $85\\%$), попробуйте явно инициализировать веса сети и настроить разные значения learning_rate для разных параметров (посмотрите примеры с занятия). Если эти меры не помогут, то стоит поискать ошибку. Да, обучение нейронных сетей — это непростой процесс." ] }, { "cell_type": "code", "execution_count": null, "id": "448e9876", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "209a8dc9", "metadata": {}, "source": [ "\n", "\n", "**3.** Теперь самое интересное — заглянем под капот нашей нейросети!\n", "\n", "" ] }, { "cell_type": "markdown", "id": "501e052f", "metadata": {}, "source": [ "Для более глубокого понимания работы нейросети **визуализируем матрицы весов** для нескольких итераций обучения. Тем самым, вы сможете оценить, как меняется поведение модели в процессе обучения.\n", "\n", "1. Выберите примерно 10 итераций обучения, включая первую и последнюю.\n", "2. Для каждого слоя настройте график, используя предоставленный шаблон.\n", "\n", "Обратите внимание, что для корректной визуализации матрицы весов ее необходимо **транспонировать**. Например, для первого слоя вертикальная ось должна соответствовать входу сети, а горизонтальная — промежуточной размерности.\n", "\n", "В качестве цветовой палитры выбрана `\"RdBu\"`, что позволяет отображать положительные числа матрицы красным цветом, а отрицательные — синим. Чтобы достичь этого, также **необходимо установить параметры `vmin` и `vmax`** симметрично относительно нуля, чтобы нули отображались белым цветом. Подберите эти значения таким образом, чтобы получить наиболее четкую картинку, на которой выделяются определенные группы пикселей." ] }, { "cell_type": "code", "execution_count": null, "id": "6c10c03f", "metadata": {}, "outputs": [], "source": [ "# Для каждого слоя\n", "\n", "plt.figure(figsize=(12, 10))\n", "for ...: # по выбранным итерациям\n", " plt.subplot(...) # для отображения всех итераций в ряд\n", " plt.imshow(..., cmap=\"RdBu\", vmin=..., vmax=...) # настройте vmin = -vmax\n", " plt.title(...) # укажите номер итерации\n", " plt.xticks([]) # уберем координаты по x\n", " plt.yticks([]) # уберем координаты по y" ] }, { "cell_type": "markdown", "id": "4b3c0ecd", "metadata": {}, "source": [ "Какие выводы можно сделать, основываясь на полученных визуализациях? \n", "Подумайте, почему на больших итерациях для первого слоя начинают выделяться определенные пиксели? Почему именно эти пиксели? \n", "\n", "*Подсказка: вспомните, как устроены входные изображения цифр, с которыми мы работаем.*" ] }, { "cell_type": "markdown", "id": "1853eadb", "metadata": {}, "source": [ "**Ответ:**\n", "\n", "..." ] }, { "cell_type": "markdown", "id": "98ab00b0", "metadata": {}, "source": [ "\n", "\n", "Попробуйте **улучшить точность классификации**, рассмотрев больше нейронов в промежуточном слое." ] }, { "cell_type": "code", "execution_count": null, "id": "27ed8310", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "9c9dbef1", "metadata": {}, "source": [ "Теперь попробуйте добавить к сети еще один слой." ] }, { "cell_type": "code", "execution_count": null, "id": "5774cc51", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "943d6e05", "metadata": {}, "source": [ "Насколько дольше обучается нейросеть? Получилось ли улучшить качество?" ] }, { "cell_type": "markdown", "id": "ba5cedca", "metadata": {}, "source": [ "**Ответ:**\n", "\n", "..." ] }, { "cell_type": "markdown", "id": "2f6f12b6", "metadata": {}, "source": [ "\n", "\n", "Сделайте выводы." ] }, { "cell_type": "markdown", "id": "36c7e0c8", "metadata": {}, "source": [ "**Ответ:**\n", "\n", "..." ] }, { "cell_type": "markdown", "id": "28341a21", "metadata": {}, "source": [ "\n", "\n", "---\n", "\n", "**Мы продолжим работу с нейросетями во второй части этого домашнего задания, которую вы можете сдать на неделю позже первой.**\n", "\n", "Вы могли заметить, что качество наших моделей было не таким высоким, как можно было бы получить современными моделями. Тем не менее отметим, что сравнивать с моделью KNN, которую мы рассмотрели на первом занятии, было бы некорректно, так как там использовалась более простая выборка.\n", "\n", "Обучение нейронных сетей — это довольно сложная задача. Чтобы глубокие сети обучались более стабильно, используются специальные технологии, изучение которых требует больших усилий. Например, на следующей лекции мы рассмотрим сверточные слои и другие подходы к созданию и обучению нейросетей для анализа изображений.\n", "Полное погружение в мир нейросетей, включая самые современные архитектуры, ожидает вас на третьем курсе. Например, на DS-потоке будет порядка 14 лекций по этой теме." ] }, { "cell_type": "markdown", "id": "2686624c", "metadata": {}, "source": [ "" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.12.3" } }, "nbformat": 4, "nbformat_minor": 5 }