{ "cells": [ { "cell_type": "markdown", "id": "2432dd81", "metadata": {}, "source": [ "# Введение в анализ данных\n", "## Домашнее задание 4, часть 2. Нейронные сети.\n" ] }, { "cell_type": "markdown", "id": "8b7635e7", "metadata": {}, "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", "* Решения необходимо оформить в виде $\\LaTeX$ в markdown-ячейках. Иные способы (в т.ч. фотографии) не принимаются.\n", "* Если вы не знаете $\\LaTeX$, используйте ИИ-инструменты для оформления черновика решения. Примеры были показаны на лекции 2 по ИИ-инструментам.\n", "* **В решениях поясняйте, чем вы пользуетесь**, хотя бы кратко. \n", "* Решение, в котором есть только ответ, и отсутствуют вычисления, оценивается в 0 баллов.\n", "\n", "\n", "Важно!!! Правила заполнения ноутбука:\n", "* Запрещается удалять имеющиеся в ноутбуке ячейки, менять местами положения задач.\n", "* Сохраняйте естественный линейный порядок повествования в ноутбуке сверху-вниз.\n", "* Отвечайте на вопросы, а также добавляйте новые ячейки в предложенных местах, которые обозначены `...`.\n", "* В markdown-ячейка, содержащих описание задачи, находятся специальные отметки, которые запрещается модифицировать.\n", "* При нарушении данных правил работа может получить 0 баллов.\n", "\n", "\n", "**Баллы за задание:**\n", "\n", "Легкая часть (достаточно на \"хор\"):\n", "\n", "* Задача 2 — 40 баллов.\n", "\n", "Сложная часть (необходимо на \"отл\"):\n", "\n", "* Задача 3 — 80 баллов;\n", "* Задача 4 — 50 баллов.\n", "\n", "Баллы учитываются в обязательной части курса и не влияют на оценку по факультативной части." ] }, { "cell_type": "code", "execution_count": null, "id": "20bcd868", "metadata": {}, "outputs": [], "source": [ "# Bot check\n", "\n", "# HW_ID: fpmi_ad4_part2\n", "# Бот проверит этот ID и предупредит, если случайно сдать что-то не то.\n", "\n", "# Status: not final\n", "# Перед отправкой в финальном решении удали \"not\" в строчке выше.\n", "# Так бот проверит, что ты отправляешь финальную версию, а не промежуточную.\n", "# Никакие значения в этой ячейке не влияют на факт сдачи работы." ] }, { "cell_type": "code", "execution_count": null, "id": "69fc9ce6", "metadata": {}, "outputs": [], "source": [ "from typing import Tuple\n", "import numpy as np\n", "from sklearn import datasets\n", "\n", "from IPython.display import clear_output\n", "import matplotlib.pyplot as plt\n", "from matplotlib.colors import ListedColormap\n", "import seaborn as sns\n", "\n", "...\n", "\n", "sns.set(palette=\"Set2\")\n", "cm_bright = ListedColormap([\"#FF3300\", \"#00CC66\"])" ] }, { "cell_type": "markdown", "id": "0ddebb66", "metadata": {}, "source": [ "В условии задания обозначены области, в которых запрещено использование ИИ-инструментов. Это не исчерпывающий список. Подробные правила можно найти по ссылке на сайте. В частности, запрещено применять ИИ-инструменты при решении теоретических частей задач, ответах на вопросы и написании выводов." ] }, { "cell_type": "markdown", "id": "b86f24b2", "metadata": {}, "source": [ "---\n", "### Легкая часть" ] }, { "cell_type": "markdown", "id": "20de8d13", "metadata": {}, "source": [ "---\n", "### Задача 2.\n", "\n", "**При решении задачи запрещено пользоваться ИИ-инструментами,** но можно при оформлении решения.\n", "\n", "Рассмотрим двухслойную полносвязную нейронную сеть, которая принимает на вход $x\\in\\mathbb{R}^d$ и возвращает $y\\in\\{0, 1\\}$\n", "$$y_\\theta(x) = \\sigma_2 \\big( \\sigma_1 \\left( x^{\\top} W_1 + b_1 \\right) W_2 + b_2 \\big),$$\n", "где \n", "* $W_1 \\in \\mathbb{R}^{d \\times h}, b_1 \\in \\mathbb{R}^{h}$ — параметры 1-го слоя,\n", "* $W_2 \\in \\mathbb{R}^{h \\times 1}, b_2 \\in \\mathbb{R}$ — параметры 2-го слоя,\n", "* $\\theta = (W_1, b_1, W_2, b_2)$ — все параметры нейросети,\n", "* $\\sigma_1(x) = \\tanh x = \\frac{e^{x} - e^{-x}}{e^{x} + e^{-x}}$ — гиперболический тангенс, функция активации 1-го слоя, применяется поэлементно,\n", "* $\\sigma_2(x) = \\frac{1}{1 + e^{-x}}$ — логистическая сигмоида, функция активации 2-го слоя." ] }, { "cell_type": "markdown", "id": "4d9b6739", "metadata": {}, "source": [ "**1.** Нарисуйте схематически данную нейронную сеть. Сколько у нее обучаемых параметров?\n", "\n", "*Для вставки изображения скопируйте его и вставьте в ячейку markdown с помощью `Ctrl+V`. Должно появится либо большое количество символов (Colab), либо что-то вроде `![image.png](attachment:image.png)`.*" ] }, { "cell_type": "markdown", "id": "55e5f658", "metadata": {}, "source": [ "..." ] }, { "cell_type": "markdown", "id": "c668d874", "metadata": {}, "source": [ "**2.** Дана обучающая выборка $X \\in \\mathbb{R}^{n \\times d}$ — матрица входных данных и $Y \\in \\{0, 1\\}^{n}$ — таргет. Нейронная сеть обучается по этой выборке, минимизируя заданную функцию $\\mathscr{L}$, в данном случае рассмотрим кросс энтропию\n", "$$\\mathscr{L}(\\theta) = -\\sum_{i=1}^n \\big( Y_i \\log y_\\theta(X_i) + (1-Y_i) \\log \\left(1-y_\\theta(X_i)\\right) \\big).$$\n", "\n", "Наша цель — определить оптимальные параметры нашей модели, минимизируя функцию $\\mathscr{L}(\\theta)$ на заданном наборе данных. Мы будем решать эту задачу с помощью метода градиентного спуска, который требует вычисления производных по всем параметрам сети. Конечно, в данном случае несложно выписать все производные напрямую, с чем каждый из вас легко сможет справиться.\n", "\n", "Однако прямое вычисление производных имеет квадратичную зависимость от количества параметров, что делает его неэффективным. Из-за этого мы не смогли бы быстро обучать глубокие нейронные сети. Хотя в данном случае речь идёт всего лишь о двух слоях, понимание работы методов на простых примерах было бы полезно.\n", "\n", "На лекции был рассказан метод **обратного распространения ошибки (back propagation)** и показан его пример для двухслойной нейронной сети (на доске). Выпишите все необходимые производные для применения метода back propagation." ] }, { "cell_type": "markdown", "id": "ba3ddd75", "metadata": {}, "source": [ "..." ] }, { "cell_type": "markdown", "id": "88e29bd7", "metadata": {}, "source": [ "**3.** Выпишите итоговый алгоритм обратного распространения ошибки для данной нейросети. Нужно записать все необходимые вычисления, включая все итоговые формулы производных. Подробно поясните, почему он имеет линейную сложность." ] }, { "cell_type": "markdown", "id": "e86317ef", "metadata": {}, "source": [ "..." ] }, { "cell_type": "markdown", "id": "807405d2", "metadata": {}, "source": [ "**Выводы:**" ] }, { "cell_type": "markdown", "id": "9e7ca286", "metadata": {}, "source": [ "..." ] }, { "cell_type": "markdown", "id": "f533f5b0", "metadata": {}, "source": [ "**Внимание!** Решение, которое будет иметь сверхлинейную сложность, не будет оценено совсем. Считать производные вы все умеете, задача — вычислять их эффективно алгоритмически." ] }, { "cell_type": "markdown", "id": "42a2beca", "metadata": {}, "source": [ "---\n", "### Сложная часть" ] }, { "cell_type": "markdown", "id": "fcda0dd5", "metadata": {}, "source": [ "---\n", "### Задача 3." ] }, { "cell_type": "markdown", "id": "b7d17471", "metadata": {}, "source": [ "**1.** На практике, чтобы сделать численные вычисления градиентов более стабильными, в качестве функции ошибки используют композицию бинарной кроссэнтропии и сигмоиды, передавая ей логиты в качестве аргументов, то есть выходы последнего линейного слоя. Выведите итоговую формулу градиента бинарной кроссэнтропии по выходу сети до применения сигмоиды." ] }, { "cell_type": "markdown", "id": "1cfdd76d", "metadata": {}, "source": [ "..." ] }, { "cell_type": "markdown", "id": "4a667bf8", "metadata": {}, "source": [ "**2.** Реализуйте класс обучения нейронной сети из задачи 2 в sklearn-стиле, используя только библиотеку `numpy`. Используйте представленный ниже шаблон.\n", "\n", "**Особенности:**\n", "* Функция `_backward` должна реализовывать метод back propagation полностью на основе формул из задачи 2. Функция должна *накапливать* градиенты, подробнее см. ноутбук с занятия по нейросетям.\n", "* При реализации функции `_backward` необходимо сразу посчитать производную функции ошибку по выходу сети до применения функции активации, что в коде реализуется функцией `output_gradient`. Это необходимо для повышения стабильности численных вычислений, что также используется в PyTorch. \n", "* Желательно, чтобы градиент не зависел от размера входных данных, поэтому при вычислении градиентов по параметрам, рекомендуется разделить их на размер батча.\n", "* При необходимости вы можете менять внутреннюю структуру методов, кроме методов `__init__`, `fit`, `predict`, `predict_proba`. В случае изменений исправляйте также документацию и типизацию аргументов, иначе оценка может быть снижена.\n", "* **При реализации класса запрещено пользоваться ИИ-инструментами.** За исключением случаев обновления документации класса при необходимости." ] }, { "cell_type": "code", "execution_count": null, "id": "81d26f9c", "metadata": {}, "outputs": [], "source": [ "# При реализации класса запрещено пользоваться ИИ-инструментами.\n", "\n", "\n", "class TwoLayersNNClassifier:\n", " \"\"\"Двухслойная нейронная сеть для задачи бинарной классификации.\n", "\n", " Параметры:\n", " input_size (int): Размер входного вектора.\n", " hidden_size (int): Количество нейронов в скрытом слое.\n", " learning_rate (float, optional): Скорость обучения (по умолчанию 0.01).\n", " n_epoch (int, optional): Количество эпох обучения (по умолчанию 100).\n", " weight_init_scale (float, optional): Масштаб для инициализации весов (по умолчанию 1).\n", " \"\"\"\n", "\n", " def __init__(\n", " self,\n", " input_size: int,\n", " hidden_size: int,\n", " learning_rate: float = 0.01,\n", " n_epoch: int = 100,\n", " weight_init_scale: float = 1,\n", " ):\n", " self.learning_rate = learning_rate\n", " self.n_epoch = n_epoch\n", " self.input_size = input_size\n", " self.hidden_size = hidden_size\n", " self.weight_init_scale = weight_init_scale\n", "\n", " # Инициализация весов\n", " self._initialize_weights()\n", "\n", " # Инициализация градиентов\n", " self._zero_grad()\n", "\n", " # Для хранения истории потерь\n", " self.loss_history = []\n", "\n", " def _initialize_weights(self):\n", " \"\"\"Инициализирует веса сети случайными значениями.\"\"\"\n", " self.W1 = self._init_weight_matrix(self.input_size, self.hidden_size)\n", " self.b1 = self._init_weight_matrix(1, self.hidden_size)\n", " self.W2 = self._init_weight_matrix(self.hidden_size, 1)\n", " self.b2 = self._init_weight_matrix(1, 1)\n", "\n", " def _init_weight_matrix(self, rows: int, cols: int) -> np.ndarray:\n", " \"\"\"Инициализирует матрицу весов случайными значениями.\n", "\n", " Параметры:\n", " rows (int): Количество строк.\n", " cols (int): Количество столбцов.\n", "\n", " Возвращает:\n", " np.ndarray: Матрица весов.\n", " \"\"\"\n", " return (np.random.rand(rows, cols) * 2 - 1) * self.weight_init_scale\n", "\n", " @staticmethod\n", " def tanh_derivative(x: np.ndarray) -> np.ndarray:\n", " \"\"\"Вычисляет производную функции гиперболического тангенса.\n", "\n", " Параметры:\n", " x (np.ndarray): Входной массив.\n", "\n", " Возвращает:\n", " np.ndarray: Производная функции tanh.\n", " \"\"\"\n", " return ...\n", "\n", " @staticmethod\n", " def sigmoid(x: np.ndarray, scale: int = 1000) -> np.ndarray:\n", " \"\"\"Вычисляет логистическую сигмоиду.\n", "\n", " Параметры:\n", " x (np.ndarray): Входной массив.\n", " scale (int, optional): Максимальное абсолютное значение для ограничения (по умолчанию 1000).\n", "\n", " Возвращает:\n", " np.ndarray: Значения сигмоиды.\n", " \"\"\"\n", " x = np.clip(x, -scale, scale) # Ограничиваем значения\n", " return 1 / (1 + np.exp(-x))\n", "\n", " @staticmethod\n", " def binary_crossentropy(y_pred: np.ndarray, y_true: np.ndarray) -> float:\n", " \"\"\"Вычисляет значение бинарной кросс-энтропии.\n", "\n", " Параметры:\n", " y_pred (np.ndarray): Предсказанные значения (вероятности).\n", " y_true (np.ndarray): Истинные значения (0 или 1).\n", "\n", " Возвращает:\n", " float: Значение бинарной кросс-энтропии.\n", " \"\"\"\n", "\n", " # Вычислите кросс-энтропию\n", " # Используйте подход с logsumexp (см. ДЗ 3, задача 5)\n", " loss = ...\n", " return loss\n", "\n", " @staticmethod\n", " def output_gradient(logits: np.ndarray, y: np.ndarray) -> np.ndarray:\n", " \"\"\"Вычисляет градиент по выходу сети до применения сигмоиды.\n", "\n", " Параметры:\n", " logits (np.ndarray): Предсказанные значения.\n", " y (np.ndarray): Истинные значения.\n", "\n", " Возвращает:\n", " np.ndarray: Градиент по выходу до применения сигмоиды.\n", " \"\"\"\n", "\n", " # Вычислите градиент по выходу сети до применения сигмоиды.\n", " # Спойлер: получится очень простая формула\n", " return ...\n", "\n", " def _forward(self, X: np.ndarray) -> np.ndarray:\n", " \"\"\"Выполняет прямой проход по сети.\n", "\n", " Параметры:\n", " X (np.ndarray): Входные данные.\n", "\n", " Возвращает:\n", " np.ndarray: Выход сети (вероятности).\n", " \"\"\"\n", "\n", " return ...\n", "\n", " def _backward(self, X: np.ndarray, y: np.ndarray):\n", " \"\"\"Выполняет обратный проход (backpropagation), накапливает градиенты.\n", "\n", " Параметры:\n", " X (np.ndarray): Входные данные.\n", " y (np.ndarray): Истинные значения.\n", " \"\"\"\n", "\n", " # Градиент по выходу до применения сигмоиды\n", " ds2 = ...\n", "\n", " # Градиент по параметрам 2-го слоя\n", " ...\n", "\n", " # Градиент по выходу скрытого слоя\n", " ...\n", "\n", " # Градиент по параметрам 1-го слоя\n", " ...\n", "\n", " def _step(self):\n", " \"\"\"Обновляет веса сети на основе вычисленных градиентов.\"\"\"\n", "\n", " # Реализуйте шаг градиентного спуска\n", " ...\n", "\n", " def _zero_grad(self):\n", " \"\"\"Обнуляет градиенты перед следующим шагом обучения.\"\"\"\n", " self.dW1 = np.zeros_like(self.W1)\n", " self.db1 = np.zeros_like(self.b1)\n", " self.dW2 = np.zeros_like(self.W2)\n", " self.db2 = np.zeros_like(self.b2)\n", "\n", " def _show_progress(self, epoch: int, plot_freq: int):\n", " \"\"\"Выводит прогресс обучения на каждой итерации.\n", "\n", " Параметры:\n", " epoch (int): Номер текущей эпохи.\n", " plot_freq (int): Частота отрисовки графика.\n", " \"\"\"\n", " info_text = f\"Эпоха {epoch + 1}/{self.n_epoch}, Лосс: {self.loss_history[-1]:.4f}\"\n", "\n", " if plot_freq == 0:\n", " clear_output(wait=True) # Очистка вывода\n", " print(info_text)\n", "\n", " elif (epoch + 1) % plot_freq == 0:\n", " clear_output(wait=True) # Очистка вывода\n", " plt.figure(figsize=(8, 4))\n", " plt.plot(self.loss_history)\n", " plt.xlabel(\"Номер эпохи\")\n", " plt.ylabel(\"Лосс\")\n", " plt.title(info_text)\n", " plt.show()\n", "\n", " def fit(self, X: np.ndarray, y: np.ndarray, verbose: bool = True, plot_freq: int = 0):\n", " \"\"\"Обучает модель на предоставленных данных.\n", "\n", " Параметры:\n", " X (np.ndarray): Входные данные.\n", " y (np.ndarray): Истинные значения (0 или 1).\n", " verbose (bool, optional): Если True, выводит прогресс обучения (по умолчанию True).\n", " plot_freq (int, optional): Частота отрисовки графика.\n", " Если 0, график не рисуется (по умолчанию 0).\n", " \"\"\"\n", " y = y[:, None]\n", " self.loss_history = [] # Очистка истории потерь перед обучением\n", "\n", " for epoch in range(self.n_epoch):\n", " # Прямой проход по сети\n", " y_pred = self._forward(X)\n", " loss = self.binary_crossentropy(y_pred, y)\n", " self.loss_history.append(loss) # Сохраняем значение потерь\n", "\n", " # Допишите шаги обучения\n", " ...\n", "\n", " if verbose:\n", " self._show_progress(epoch, plot_freq)\n", "\n", " def predict(self, X: np.ndarray) -> np.ndarray:\n", " \"\"\"Предсказывает классы для входных данных.\n", "\n", " Параметры:\n", " X (np.ndarray): Входные данные.\n", "\n", " Возвращает:\n", " np.ndarray: Предсказанные классы (0 или 1).\n", " \"\"\"\n", " y_pred = ...\n", " return (y_pred > 0.5).astype(int) # Порог 0.5 для бинарной классификации\n", "\n", " def predict_proba(self, X: np.ndarray) -> np.ndarray:\n", " \"\"\"Возвращает вероятности принадлежности к классам 0 и 1.\n", "\n", " Параметры:\n", " X (np.ndarray): Входные данные.\n", "\n", " Возвращает:\n", " np.ndarray: Матрица вероятностей формы (n, 2), где n — количество объектов.\n", " Первый столбец — вероятность класса 0, второй — класса 1.\n", " \"\"\"\n", " prob_class_1 = ... # Вероятность класса 1\n", " prob_class_0 = ... # Вероятность класса 0\n", " return np.hstack((prob_class_0, prob_class_1)) # Объединяем в матрицу (n, 2)" ] }, { "cell_type": "markdown", "id": "1d1fddf9", "metadata": {}, "source": [ "**3.** Сгенерируем искусственные данные" ] }, { "cell_type": "code", "execution_count": null, "id": "20854435", "metadata": {}, "outputs": [], "source": [ "# Генерация данных\n", "n_samples = 1024\n", "X, y = datasets.make_circles(n_samples=n_samples, factor=0.5, noise=0.05)\n", "\n", "# Визуализация данных\n", "plt.figure(figsize=(5, 5))\n", "plt.scatter(X[:, 0], X[:, 1], c=y, cmap=cm_bright)\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "2a681ce3", "metadata": {}, "source": [ "Обучите реализованную ранее нейросеть решать задачу классификации на основе этой выборки.\n", "\n", "*Периодически во время обучения рисуйте график лосса. Если он возрастает, стоит поискать ошибку.*" ] }, { "cell_type": "code", "execution_count": null, "id": "4559697b", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "16fbd5f9", "metadata": {}, "source": [ "С помощью функции `np.meshgrid` создайте двухмерную сетку в диапазоне $[-1.5, 1.5]$ по каждой координате с шагом не более $0.02$. Для каждой точки сетки посчитайте оценки вероятностей принадлежности тому или иному классу. " ] }, { "cell_type": "code", "execution_count": null, "id": "fb66e929", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "0ad5a4be", "metadata": {}, "source": [ "Визуализируйте полученные предсказания классов и вероятностей.\n", "\n", "*Если у вас получилось плохое качество, попробуйте увеличить количество нейронов. Если не помогает, возможно, где-то ошибка.*" ] }, { "cell_type": "code", "execution_count": null, "id": "d0efd192", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "6f8a72f2", "metadata": {}, "source": [ "Немного увеличим разброс данных" ] }, { "cell_type": "code", "execution_count": null, "id": "37f3b43b", "metadata": {}, "outputs": [], "source": [ "# Генерация данных\n", "n_samples = 1024\n", "X, y = datasets.make_circles(n_samples=n_samples, factor=0.5, noise=0.25)\n", "\n", "# Визуализация данных\n", "plt.figure(figsize=(5, 5))\n", "plt.scatter(X[:, 0], X[:, 1], c=y, cmap=cm_bright)\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "0c9a1eaa", "metadata": {}, "source": [ "Повторите те же действия с новыми данными." ] }, { "cell_type": "code", "execution_count": null, "id": "45bb49af", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "c877cb10", "metadata": {}, "source": [ "**Выводы:**" ] }, { "cell_type": "markdown", "id": "5e0faa11", "metadata": {}, "source": [ "..." ] }, { "cell_type": "markdown", "id": "513cce86", "metadata": {}, "source": [ "**4.** Теперь давайте применим нашу нейронную сеть к данным MNIST.\n", "\n", "Загрузите данные. Для этого воспользуйтесь кодом из задачи 1. Обратите внимание, что поскольку наша сеть реализована на `numpy` и принимает `numpy`-матрицы, потребуется предварительно перевести `torch`-тензоры в `numpy`-массивы.\n", "\n", "Поскольку наша сеть была разработана для бинарной классификации, мы будем рассматривать изображения, на которых представлены только две цифры: `0` и `1`. Возьмите по 1000 изображений каждого символа и преобразуйте их в векторы. Также выделите изображения из тестовой выборки, соответствующие этим цифрам." ] }, { "cell_type": "code", "execution_count": null, "id": "bfe44779", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "afda6345", "metadata": {}, "source": [ "Обучите реализованную ранее нейронную сеть к этим данным.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "019ac6a8", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "3ada74e7", "metadata": {}, "source": [ "Посчитайте качество обученной нейросети на тестовой выборке. Можете ли вы сказать, насколько хорошо сеть обучилась?" ] }, { "cell_type": "code", "execution_count": null, "id": "4c82be74", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "8bd2d8fa", "metadata": {}, "source": [ "**Выводы:**" ] }, { "cell_type": "markdown", "id": "4d195685", "metadata": {}, "source": [ "..." ] }, { "cell_type": "markdown", "id": "7ec6df8c", "metadata": {}, "source": [ "---\n", "### Задача 4." ] }, { "cell_type": "markdown", "id": "368db68b", "metadata": {}, "source": [ "Рассмотрим глубокую нейронную сеть, которая\n", "* принимает на вход $x\\in\\mathbb{R}^d$,\n", "* возвращает $y\\in\\{0, 1\\}$,\n", "* содержит $99$ полносвязных слоев (`Linear`) с функцией активации $\\tanh$, за исключением последнего слоя,\n", "* последний слой использует функцию активации логистическая сигмоида.\n", "\n", "В качестве лосса $\\mathscr{L}(\\theta)$ возьмем кросс-энтропию.\n", "\n", "**1.** Распишите градиент лосса по весам первого слоя $W_1$. Какова сложность этой операции?" ] }, { "cell_type": "markdown", "id": "5d6787b7", "metadata": {}, "source": [ "..." ] }, { "cell_type": "markdown", "id": "a2aa0d97", "metadata": {}, "source": [ "Нарисуйте график функции активации и ее производной." ] }, { "cell_type": "code", "execution_count": null, "id": "180be637", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "a5088b4a", "metadata": {}, "source": [ "На основе графика проанализируйте, что будет происходить с градиентом по весам первого слоя, если нейросеть \"ненулевая\", иначе говоря, на промежуточных слоях может выдавать различные значения, в том числе довольно большие." ] }, { "cell_type": "markdown", "id": "d40aea64", "metadata": {}, "source": [ "..." ] }, { "cell_type": "markdown", "id": "dd915559", "metadata": {}, "source": [ "**2.** Реализуйте эту нейросеть на **PyTorch**.\n", "\n", "*Примечание.* Каждый слой должен быть инициализирован отдельно. Нельзя создавать копии уже инициализированного слоя." ] }, { "cell_type": "code", "execution_count": null, "id": "8a79a46c", "metadata": {}, "outputs": [], "source": [ "# При реализации нейросети запрещено пользоваться ИИ-инструментами.\n", "\n", "..." ] }, { "cell_type": "markdown", "id": "8e7c95ba", "metadata": {}, "source": [ "Аналогично задаче 3 попробуйте обучить эту нейросеть для классификации изображений с цифрами 0 и 1 из датасета MNIST. Скорее всего весь датасет не поместится в память, поэтому предлагается использовать случайную подвыборку (батч) размера 32 на каждом шаге обучения." ] }, { "cell_type": "code", "execution_count": null, "id": "b5b04abc", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "6b832a87", "metadata": {}, "source": [ "Постройте график зависимости среднего и максимального абсолютного значения градиента по весам для каждого слоя от номера слоя." ] }, { "cell_type": "code", "execution_count": null, "id": "4375c285", "metadata": {}, "outputs": [], "source": [ "..." ] }, { "cell_type": "markdown", "id": "feca2879", "metadata": {}, "source": [ "**Выводы:**" ] }, { "cell_type": "markdown", "id": "751a68df", "metadata": {}, "source": [ "..." ] }, { "cell_type": "markdown", "id": "b19bc418", "metadata": {}, "source": [ "*Примечание.* Подобная ситуация известна как \"паралич\" нейросети. Именно поэтому такие функции активации, как ReLU, стали очень популярными. Для преодоления этого паралича существует множество методов, многие из которых мы рассмотрим на 3 курсе." ] } ], "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 }