{
"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), либо что-то вроде ``.*"
]
},
{
"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
}