From e924106ec843f46f5fdff8082c3269a85f5da605 Mon Sep 17 00:00:00 2001 From: Dmitrii Krosh Date: Tue, 2 Jun 2026 03:45:51 +0300 Subject: [PATCH] last --- ...Research]_Clusterization_[2025_2026].ipynb | 1819 +++++++++++++++++ .../requirements_2025_26_for_colab_small.txt | 22 + 2 files changed, 1841 insertions(+) create mode 100644 task31/[Research]_Clusterization_[2025_2026].ipynb create mode 100644 task31/requirements_2025_26_for_colab_small.txt diff --git a/task31/[Research]_Clusterization_[2025_2026].ipynb b/task31/[Research]_Clusterization_[2025_2026].ipynb new file mode 100644 index 0000000..c22320f --- /dev/null +++ b/task31/[Research]_Clusterization_[2025_2026].ipynb @@ -0,0 +1,1819 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "VFxYDW1SM-r0", + "metadata": { + "id": "VFxYDW1SM-r0" + }, + "source": [ + "# \n", + "\n", + "# Машинное обучение. ВМК МГУ" + ] + }, + { + "cell_type": "markdown", + "id": "myHxcHVTNDfn", + "metadata": { + "id": "myHxcHVTNDfn" + }, + "source": [ + "# Практическое задание 7: Кластеризация. Методы снижения размерности.\n", + "## Уровень: **Исследовательский (Research)**\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "fKpuBwx27X2n", + "metadata": { + "id": "fKpuBwx27X2n" + }, + "source": [ + "# О формате сдачи\n", + "\n", + "🔷 **При решении ноутбука используйте данный шаблон**\n", + "\n", + " ✅ Можно добавлять новые ячейки любых типов\n", + " ❌ Не нужно удалять текстовые ячейки c разметкой частей ноутбука и формулировками заданий\n", + "\n", + "\n", + "🔷 **При оценивании задач учитывается код**\n", + "\n", + " ✅ Задания, в которых необходим код, обычно помечаются фразами \"Your code here\"/\"Ваш код\" и аналогичными\n", + " ❌ Ответы на вопросы без сопутствующего кода оцениваются в 0 баллов\n", + " ❌ Наличе работоспособного кода в ноутбуке, если на сказано иного, обязательно\n", + "\n", + "🔷 **При оценивании задач учитываются выводы**\n", + "\n", + " ✅ Задания, в которых необходимы выводы, обычно помечаются фразами Вывод\"/\"Ответ на вопрос\"/\"Ваш текст\" и аналогичными\n", + " ✅ Обычно выводы подразумевают под собой текстовый ответ (можно писать markdown, latex).\n", + " ✅ Сопутствующие изображения, графики, таблички - приветствуются!\n", + " ❌ При отсутствии выводов задание не засчитается на полный балл\n", + "\n", + "-----------\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "baEqASDy7X2o", + "metadata": { + "id": "baEqASDy7X2o" + }, + "source": [ + "__В этом задании вы..:__\n", + "\n", + "* Познакомитесь с одним способом визуализации процесса обучения\n", + "* Сравните между собой результаты разных способов кластеризации\n", + "* Посмотрите и реализуете несколько метрик качества кластеризации\n", + "* Попробуете разные методы снижения размерности\n", + "\n", + "----\n", + "\n", + "\n", + "**Примерное время выполнения (execution time/время выполнения, если нажать run all) всех ячеек ноутбука при правильной реализации: 60 минут **" + ] + }, + { + "cell_type": "markdown", + "id": "Fl6R5pHXNWVc", + "metadata": { + "id": "Fl6R5pHXNWVc" + }, + "source": [ + "----------------------------------------------\n", + "" + ] + }, + { + "cell_type": "markdown", + "id": "df956837", + "metadata": { + "id": "df956837" + }, + "source": [ + "Перед началом выполнения переведите ноутбук в `Доверенный режим` (`Trusted`) для корректного отображения изображений:\n", + "\n", + "\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0ef4020", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T13:59:34.820566Z", + "start_time": "2023-03-01T13:59:34.788142Z" + }, + "id": "d0ef4020" + }, + "outputs": [], + "source": [ + "%config Completer.use_jedi = False\n", + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "markdown", + "id": "VEIxThGGNcxw", + "metadata": { + "id": "VEIxThGGNcxw" + }, + "source": [ + "----------------------------------------------\n", + "" + ] + }, + { + "cell_type": "markdown", + "id": "dGuTHcED7i4j", + "metadata": { + "id": "dGuTHcED7i4j" + }, + "source": [ + "# Подготовка рабочей среды\n", + "\n", + "Сначала установим нужные нам версии библиотек. Мы гарантируем, что в данных версиях задание будет корректно отрабатывать.\n", + "\n", + "После установки нужных версий, **возможно,** нужно перезагрузить среду (runtime), но скорее всего вам это не понадобится\n", + "\n", + "\n", + "На скачивание файла и установку понадобится не более 5 минут.\n", + "\n", + "**Важно!**\n", + "\n", + "Устанавливать нужные версии нужно каждый раз, когда создается новый рантайм. Например, если вы 2 часа подряд делаете это задание, то подготовить библиотеки достаточно 1 раз. Но если вы, например, начали в понедельник, затем закрыли/выключили ноутбук, то при продолжении в среду, вам нужно будет запустить рантайм заново и следовательно заново установить библиотеки.\n", + "\n", + "**Важно!**\n", + "Если вы предпочитаете делать практические задания на своем личном ноутбуке, то проверьте, что вы установили рабочее окружение в [соответствии с гайдом](https://github.com/MSU-ML-COURSE/ML-COURSE-24-25/blob/main/tutorials/%D0%A2%D1%83%D1%82%D0%BE%D1%80%D0%B8%D0%B0%D0%BB%20%D0%BF%D0%BE%20%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B5%20%D1%80%D0%B0%D0%B1%D0%BE%D1%87%D0%B5%D0%B3%D0%BE%20%D0%BE%D0%BA%D1%80%D1%83%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B2%20Python%20%D0%B4%D0%BB%D1%8F%20%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F%20%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%20(2).pdf)\n", + "\n", + "-----\n", + "\n", + "**Важно!** В этом задании мы будем использовать полное виртуальное окружение, так как понадобятся библиотеки `torch` и `tensorflow`\n", + "\n", + "Обратите внимание, что установка `torch` и `tensorflow` через `pip `может сломать ваше окружение, особенно если вы используете GPU. Выполняйте их установку в соответствии с Вашей конфигурацией системы или в отдельном виртуальном окружении" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "-gSWrzAD7iIq", + "metadata": { + "id": "-gSWrzAD7iIq" + }, + "outputs": [], + "source": [ + "! curl https://raw.githubusercontent.com/MSU-ML-COURSE/ML-COURSE-25-26/refs/heads/master/requirements/requirements.txt -o ./requirements_2025_26_for_colab_small.txt\n", + "! pip install -q -r ./requirements_2025_26_for_colab_small.txt" + ] + }, + { + "cell_type": "markdown", + "id": "92056c3a", + "metadata": {}, + "source": [ + "i use arch btw" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "Th8Xxd4aA-vC", + "metadata": { + "id": "Th8Xxd4aA-vC" + }, + "outputs": [], + "source": [ + "import catboost" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34062f10", + "metadata": {}, + "outputs": [], + "source": [ + "import torch" + ] + }, + { + "cell_type": "markdown", + "id": "KVECRShlPiMo", + "metadata": { + "id": "KVECRShlPiMo" + }, + "source": [ + "Теперь можно приступать к выполнению задания! :)" + ] + }, + { + "cell_type": "markdown", + "id": "GDOxRUyMPl6I", + "metadata": { + "id": "GDOxRUyMPl6I" + }, + "source": [ + "-----------\n", + "" + ] + }, + { + "cell_type": "markdown", + "id": "c431261c", + "metadata": { + "ExecuteTime": { + "end_time": "2021-08-27T20:30:10.688162Z", + "start_time": "2021-08-27T20:30:10.590165Z" + }, + "id": "c431261c" + }, + "source": [ + "# 1 О задании" + ] + }, + { + "cell_type": "markdown", + "id": "18f2f660", + "metadata": { + "id": "18f2f660" + }, + "source": [ + "В данной работе вам предстоит познакомится с методами машинного обучения без учителя — кластеризацией и алгоритмами снижения размерности." + ] + }, + { + "cell_type": "markdown", + "id": "6e2a6a58", + "metadata": { + "id": "6e2a6a58" + }, + "source": [ + "Рекомендуется использовать Kaggle так как в нём корректно работают интерактивные визуализации." + ] + }, + { + "cell_type": "markdown", + "id": "4f1d3166", + "metadata": { + "id": "4f1d3166" + }, + "source": [ + "Здесь перечислены основные функции и библиотеки, которые могут понадобиться Вам в процессе выполнения задания. Подключение других библиотек возможно, но нежелательно. **Работа каких-либо других библиотек не гарантируется.**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "454c2b3b", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T22:39:16.136071Z", + "start_time": "2023-03-01T22:39:02.734480Z" + }, + "id": "454c2b3b" + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "import gdown\n", + "\n", + "import scipy\n", + "\n", + "import numpy as np\n", + "\n", + "import tqdm.auto as tqdm\n", + "\n", + "import matplotlib\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib.offsetbox import OffsetImage, AnnotationBbox\n", + "\n", + "from ipywidgets import interactive, fixed, interact_manual, IntSlider, FloatLogSlider, FloatSlider\n", + "\n", + "import torch\n", + "from torchvision.datasets import CIFAR10\n", + "\n", + "# Необходима преварительная установка tensorflow\n", + "from keras.applications.inception_v3 import InceptionV3, preprocess_input\n", + "\n", + "import sklearn\n", + "\n", + "from sklearn.decomposition import KernelPCA\n", + "from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering\n", + "\n", + "# Библиотека umap-learn, а не umap\n", + "from umap import UMAP\n", + "from sklearn.manifold import TSNE, Isomap\n", + "\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.datasets import make_classification, make_moons, make_blobs\n", + "from sklearn.preprocessing import StandardScaler, MinMaxScaler\n", + "\n", + "from warnings import simplefilter\n", + "from sklearn.exceptions import ConvergenceWarning\n", + "simplefilter(\"ignore\", category=ConvergenceWarning)" + ] + }, + { + "cell_type": "markdown", + "id": "664fdc07", + "metadata": { + "id": "664fdc07" + }, + "source": [ + "Определим вспомогательную функцию для отрисовки двумерных кластеризованных данных. При выполенении задания желательно пользоваться этой функцией для визуализации. При необходимости можете менять сигнатуру и поведение функции как вам удобно, _оставляя стиль отрисовки в целом неизменным_." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "edcc67b5", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T14:05:19.377145Z", + "start_time": "2023-03-01T14:05:19.292571Z" + }, + "code_folding": [], + "id": "edcc67b5" + }, + "outputs": [], + "source": [ + "def plot_2d_data(data, labels, title='Исходные данные', cmap='tab20', ax=None):\n", + " '''\n", + " Отрисовка 2d scatter plot.\n", + " :param np.ndarray data: 2d массив точек\n", + " :param Union[list, np.ndarray] labels: список меток для каждой точки выборки\n", + " :param str title: Заголовок графика\n", + " :param str cmap: Цветовая палитра\n", + " :param ax Optional[matplotlib.axes.Axes]: Оси для отрисовки графика.\n", + " Если оси не заданы, то создаётся новая фигура и сразу же происходит её отрисовка\n", + " Иначе, график добавляется на существуюущие оси. Отрисовки фигуры не происходит\n", + " '''\n", + " n_clusters = len(np.unique(labels))\n", + "\n", + " if ax is None:\n", + " fig, ax = plt.subplots(1, 1, figsize=(10, 5))\n", + " else:\n", + " fig = None\n", + "\n", + " scatter = ax.scatter(\n", + " data[:, 0], data[:, 1], c=labels,\n", + " cmap=plt.get_cmap(cmap, n_clusters)\n", + " )\n", + "\n", + " cbar = plt.colorbar(scatter, label='Номер кластера', ax=ax)\n", + " cbar.set_ticks(np.min(labels) + (np.arange(n_clusters) + 0.5) * (n_clusters - 1) / n_clusters)\n", + " cbar.set_ticklabels(np.unique(labels))\n", + "\n", + " ax.set_title(title)\n", + " ax.grid(True)\n", + "\n", + " if fig is not None:\n", + " fig.tight_layout()\n", + " plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "EgriKAd1PP1p", + "metadata": { + "id": "EgriKAd1PP1p" + }, + "source": [ + "Также используйте написанные реализации из [Base] задания:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9smT4T9jPUc9", + "metadata": { + "id": "9smT4T9jPUc9" + }, + "outputs": [], + "source": [ + "def silhouette_score(x, labels):\n", + " '''\n", + " :param np.ndarray x: Непустой двумерный массив векторов-признаков\n", + " :param np.ndarray labels: Непустой одномерный массив меток объектов\n", + " :return float: Коэффициент силуэта для выборки x с метками labels\n", + " '''\n", + "\n", + " # Ваш код здесь:\(º □ º l|l)/\n", + "\n", + " return sil_score\n", + "\n", + "def bcubed_score(true_labels, predicted_labels):\n", + " '''\n", + " :param np.ndarray true_labels: Непустой одномерный массив меток объектов\n", + " :param np.ndarray predicted_labels: Непустой одномерный массив меток объектов\n", + " :return float: B-Cubed для объектов с истинными метками true_labels и предсказанными метками predicted_labels\n", + " '''\n", + "\n", + " # Ваш код здесь:\(º □ º l|l)/\n", + "\n", + " return score" + ] + }, + { + "cell_type": "markdown", + "id": "c5b91971", + "metadata": { + "id": "c5b91971" + }, + "source": [ + "## 1.1 Ещё несколько важных замечаний" + ] + }, + { + "cell_type": "markdown", + "id": "ec58d95f", + "metadata": { + "id": "ec58d95f" + }, + "source": [ + "При выполнении задания запрещено:\n", + "1. Менять те seed, которые явно указаны в коде\n", + "2. Менять прототипы функций, классов, методов классов\n", + "3. Менять константы, используемые для генерации выборок" + ] + }, + { + "cell_type": "markdown", + "id": "80e5b836", + "metadata": { + "id": "80e5b836" + }, + "source": [ + "При оформлении задания обратите внимание на форматирование кода и на оформление графиков:\n", + "\n", + "* Весь код должен быть оформлен в строгом соответствии с [PEP8](https://pep8.org/)\n", + "\n", + "Графики должны быть с одной стороны понятными и информативными, а с другой стороны *красивыми*. Вот несколько пунктов, которые помогут удовлетворить этим требования:\n", + "1. Все графики должны быть отрисованы в **векторном формате**. Обратите внимание, что смена режима графиков с динамического на статический и обратно может приводить к сбросу параметров отрисовки графиков. Переход в векторный режим можно выполнить с помощью команды `matplotlib_inline.backend_inline.set_matplotlib_formats('pdf', 'svg')`. Если изображения в векторном формате приводят к слишком большому размеру Jupyter Notebook можете использовать растровые изображения с **высоким dpi**. Напирмер, можно установить глобальный dpi в matplotlib: `matplotlib.rcParams['figure.dpi'] = 300`\n", + "2. На всех графиках без исключения должна быть нарисована сетка\n", + "3. Все графики и группы графиков должны иметь заголовок (`title`)\n", + "4. При необходимости оси должны быть подписаны\n", + "5. Если на графике отображено несколько сущностей (линии/точки/bar разных цветов, формы и так далее), то необходима исчерпывающая легенда\n", + "6. Все линии на графиках должны быть чётко видны (нет похожих цветов или цветов, сливающихся с фоном и так далее)\n", + "7. Масштаб по каждой оси на графике должен быть выбран правильно. Используйте масштабы `log`, `symlog` по необходимости\n", + "8. Если отображена величина, имеющая очевидный диапазон значений (например, проценты могут быть от 0 до 100), то желательно масштабировать ось на весь диапазон значений (исключением является случай, когда вам необходимо показать малое отличие, которое незаметно в таких масштабах)\n", + "9. Частота отметок по каждой оси должна быть тщательно подобрана, по необходимости задавайте `[xy]ticks`, `[xy]ticklabels` вручную. Подписи тиков на осях не должны сливаться как на одной оси, так и между ними\n", + "10. Помните, что matplotlib умеет выполнять [рендеринг Latex](https://matplotlib.org/stable/gallery/text_labels_and_annotations/tex_demo.html). Используйте эту возможность для написания формул в заголовках, легенде и в подписях осей\n", + "11. Используйте *красивую* цветовую палитру с хорошо различимыми цветами. Примеры цветовых палитр можно посмотреть [здесь](https://matplotlib.org/stable/gallery/color/colormap_reference.html). При наличи особенностей восприятия цвета можно использовать специальные палитры:\n", + "```python\n", + "plt.style.use('seaborn-colorblind')\n", + "# Или\n", + "plt.style.use('tableau-colorblind10')\n", + "# Затем, при отрисовке графиков не используйте параметр cmap\n", + "```\n", + "12. Графики должны быть не супер-микро и не супер-макро по размерам, так, чтобы можно было увидеть все, что нужно" + ] + }, + { + "cell_type": "markdown", + "id": "IsIaogfeNZMr", + "metadata": { + "id": "IsIaogfeNZMr" + }, + "source": [ + "----------------------------------------------\n", + "" + ] + }, + { + "cell_type": "markdown", + "id": "6a004841", + "metadata": { + "ExecuteTime": { + "end_time": "2021-08-15T20:20:07.191101Z", + "start_time": "2021-08-15T20:20:07.173102Z" + }, + "id": "6a004841" + }, + "source": [ + "# 2. Кластеризация \"естественных\" данных." + ] + }, + { + "cell_type": "markdown", + "id": "80d78c9c", + "metadata": { + "ExecuteTime": { + "end_time": "2021-08-16T13:40:56.472421Z", + "start_time": "2021-08-16T13:40:53.607422Z" + }, + "id": "80d78c9c" + }, + "source": [ + "Синтетические данные имеют достаточно простую структуру, поэтому методы снижения размерности позволяют получать хорошее низкоразмерное представление с достаточно выраженными кластерами. Однако, реальные данные могут быть устроены существенно сложнее. Посмотрим как поведут себя методы снижения размерности на датасете с картинками CIFAR10." + ] + }, + { + "cell_type": "markdown", + "id": "d6959566", + "metadata": { + "ExecuteTime": { + "end_time": "2021-08-18T18:04:08.749254Z", + "start_time": "2021-08-18T18:04:08.682253Z" + }, + "id": "d6959566" + }, + "source": [ + "Загрузим датасет. Будем использовать только часть обучающей выборки, чтобы ускорить вычисления на высокоразмерных данных." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6290c0b5", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T14:14:34.544651Z", + "start_time": "2023-03-01T14:14:33.016499Z" + }, + "id": "6290c0b5" + }, + "outputs": [], + "source": [ + "cifar10_test_dataset = CIFAR10('./cifar10', train=False, download=True)\n", + "cifar10_train_dataset = CIFAR10('./cifar10', train=True, download=False)\n", + "\n", + "cifar10_labels_test = np.array(cifar10_test_dataset.targets)\n", + "cifar10_labels_train = np.array(cifar10_train_dataset.targets)\n", + "\n", + "cifar10_images_test = cifar10_test_dataset.data\n", + "cifar10_images_train = cifar10_train_dataset.data\n", + "\n", + "cifar10_images_train, _, cifar10_labels_train, _ = train_test_split(\n", + " cifar10_images_train, cifar10_labels_train,\n", + " train_size=cifar10_images_test.shape[0], stratify=cifar10_labels_train, random_state=6886\n", + ")\n", + "\n", + "cifar10_data_test = (cifar10_images_test.astype(np.float32) / 255.0).reshape([cifar10_images_test.shape[0], -1])\n", + "cifar10_data_train = (cifar10_images_train.astype(np.float32) / 255.0).reshape([cifar10_images_train.shape[0], -1])" + ] + }, + { + "cell_type": "markdown", + "id": "f02a6a78", + "metadata": { + "id": "f02a6a78" + }, + "source": [ + "Отобразим данные в проекции на две случайные оси. Для удобства воспользуемся здесь ещё одним вариантом динамического контента в jupyter notebook — при наведении на точку на графике будем отображать исходную картинку." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dfd15f60", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T14:55:46.519715Z", + "start_time": "2023-03-01T14:55:46.428281Z" + }, + "code_folding": [ + 0 + ], + "id": "dfd15f60" + }, + "outputs": [], + "source": [ + "def plot_interactive(lowd_data, images, labels, names, n_dots=1000, image_scale=1.0):\n", + " with matplotlib.rc_context(rc={\n", + " 'font.size': image_scale * matplotlib.rcParams['font.size'],\n", + " 'xtick.major.size': image_scale * matplotlib.rcParams['xtick.major.size'],\n", + " 'xtick.minor.size': image_scale * matplotlib.rcParams['xtick.minor.size'],\n", + " 'ytick.major.size': image_scale * matplotlib.rcParams['ytick.major.size'],\n", + " 'ytick.minor.size': image_scale * matplotlib.rcParams['ytick.minor.size'],\n", + "\n", + " 'axes.linewidth': image_scale * matplotlib.rcParams['axes.linewidth'],\n", + " 'grid.linewidth': image_scale * matplotlib.rcParams['grid.linewidth'],\n", + " 'patch.linewidth': image_scale * matplotlib.rcParams['patch.linewidth'],\n", + " 'xtick.major.width': image_scale * matplotlib.rcParams['xtick.major.width'],\n", + " 'xtick.minor.width': image_scale * matplotlib.rcParams['xtick.minor.width'],\n", + " 'ytick.major.width': image_scale * matplotlib.rcParams['ytick.major.width'],\n", + " 'ytick.minor.width': image_scale * matplotlib.rcParams['ytick.minor.width'],\n", + "\n", + " 'lines.markeredgewidth': image_scale * matplotlib.rcParams['lines.markeredgewidth'],\n", + " }):\n", + " fig, ax = plt.subplots(1, 1, figsize=(image_scale * 10, image_scale * 5))\n", + " fig.set_dpi(300)\n", + " ax.grid(True)\n", + "\n", + " n_clusters = len(np.unique(labels))\n", + "\n", + " scatter = plt.scatter(\n", + " lowd_data[:n_dots, 0], lowd_data[:n_dots, 1], s=image_scale * 10,\n", + " c=labels[:n_dots], cmap=plt.get_cmap('tab20', n_clusters), edgecolors='none'\n", + " )\n", + "\n", + " cbar = plt.colorbar(scatter, ax=ax, label='Название кластера')\n", + " cbar.set_ticks(np.min(labels[:n_dots]) + (np.arange(n_clusters) + 0.5) * (n_clusters - 1) / n_clusters)\n", + " cbar.set_ticklabels(names)\n", + "\n", + " offset_image = OffsetImage(images[0], zoom=image_scale * 2.0)\n", + " ann_bbox = AnnotationBbox(\n", + " offset_image, (0,0), xybox=(image_scale * 50., image_scale * 50.), xycoords='data',\n", + " boxcoords=\"offset points\", pad=0.3, arrowprops=dict(\n", + " arrowstyle='->, head_length={0:.2f}, head_width={1:.2f}'.format(\n", + " image_scale * 0.4, image_scale * 0.2\n", + " )\n", + " )\n", + " )\n", + " ax.add_artist(ann_bbox)\n", + " ax.set_title('Распределение данных CIFAR10 в проекции на 2 случайные оси')\n", + " ann_bbox.set_visible(False)\n", + "\n", + " def image_hover(event):\n", + " if scatter.contains(event)[0]:\n", + " ind, *_ = scatter.contains(event)[1][\"ind\"]\n", + " w, h = fig.get_size_inches() * fig.dpi\n", + " ws = (event.x > w / 2.) * -1 + (event.x <= w / 2.)\n", + " hs = (event.y > h / 2.) * -1 + (event.y <= h / 2.)\n", + " ann_bbox.xybox = (image_scale * 50.0 * ws, image_scale * 50.0 * hs)\n", + " ann_bbox.set_visible(True)\n", + " ann_bbox.xy =(lowd_data[ind, 0], lowd_data[ind, 1])\n", + " offset_image.set_data(images[ind])\n", + " else:\n", + " ann_bbox.set_visible(False)\n", + " fig.canvas.draw_idle()\n", + "\n", + " fig.canvas.mpl_connect('motion_notify_event', image_hover)\n", + "\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d47026c4", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T14:55:47.324878Z", + "start_time": "2023-03-01T14:55:47.221576Z" + }, + "id": "d47026c4" + }, + "outputs": [], + "source": [ + "%matplotlib ipympl\n", + "matplotlib.rcParams['figure.dpi'] = 300\n", + "\n", + "# Для работы в Google Colab нужно выполнить специфичную магию\n", + "# Обычно, она не срабатывает с первого раза, поэтому может потребоваться\n", + "# несколько раз выполнить ячейку и несколько раз попробовать нарисовать график\n", + "try:\n", + " from google.colab import output\n", + " output.enable_custom_widget_manager()\n", + "except:\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "157a31c3", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T14:56:11.717485Z", + "start_time": "2023-03-01T14:56:11.439758Z" + }, + "id": "157a31c3" + }, + "outputs": [], + "source": [ + "# Если картинка окажется слишком маленькой/большой, то поменяйте image_scale на подходящее значение\n", + "plot_interactive(\n", + " cifar10_data_train[:, [17, 64]], cifar10_images_train, cifar10_labels_train,\n", + " cifar10_test_dataset.classes, n_dots=2000, image_scale=0.35\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "4534b090", + "metadata": { + "id": "4534b090" + }, + "source": [ + "Вернёмся в статичный режим отрисовки изображений:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7068db47", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T14:57:40.572601Z", + "start_time": "2023-03-01T14:57:40.493455Z" + }, + "id": "7068db47" + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "matplotlib.rcParams['figure.dpi'] = 300" + ] + }, + { + "cell_type": "markdown", + "id": "fc40ed1a", + "metadata": { + "id": "fc40ed1a" + }, + "source": [ + "#### **Задание 2.1 [кросспроверка, 1 балл][код]**\n", + "\n", + "Воспользуйтесь алгоритмами снижения размерности `TSNE`, `UMAP`, `Isomap`, `KernelPCA` для визуализации картинок.\n", + "\n", + "Постройте визуализацию низкоразмерного представления, полученного с помощью этих моделей — изобразите четыре графика в одной строке. Во второй строке отобразите результат применения обученных моделей на тестовой выборке. Если для данного алгоритма невозможно сделать предсказания на тестовой выборке — оставьте соответствующий график пустым. Обозначьте разными цветами разные классы объектов. Для повышения производительности можете отобразить только часть выборки на графике ($1000\\text{-}2000$ объектов).\n", + "\n", + "**Замечание:** обратите внимание, что все алгоритмы снижения размерности также требуют правильного масштабирования признаков, для корректной работы и интерпретируемых результатов." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8cd1716b", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T14:57:42.465402Z", + "start_time": "2023-03-01T14:57:42.385992Z" + }, + "id": "8cd1716b" + }, + "outputs": [], + "source": [ + "# Ваш код здесь:\(º □ º l|l)/\n", + "n_samples = 2000\n", + "train_indices = np.random.choice(cifar10_data_train.shape[0], n_samples, replace=False)\n", + "test_indices = np.random.choice(cifar10_data_test.shape[0], n_samples, replace=False)\n", + "cifar10_data_train_scaled = StandardScaler().fit_transform(cifar10_data_train)\n", + "cifar10_data_test_scaled = StandardScaler().fit_transform(cifar10_data_test)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60ac0430", + "metadata": {}, + "outputs": [], + "source": [ + "X_train_sample = cifar10_data_train_scaled[train_indices]\n", + "y_train_sample = cifar10_labels_train[train_indices]\n", + "X_test_sample = cifar10_data_test_scaled[test_indices]\n", + "y_test_sample = cifar10_labels_test[test_indices]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "08a720b5", + "metadata": {}, + "outputs": [], + "source": [ + "tsne = TSNE(n_components=2, perplexity=30, n_iter=1000, n_jobs=-1)\n", + "umap_reducer = UMAP(n_components=2, n_neighbors=15, min_dist=0.1, metric='euclidean')\n", + "isomap = Isomap(n_components=2, n_neighbors=10, n_jobs=-1)\n", + "kpca = KernelPCA(n_components=2, kernel='rbf', gamma=None,\n", + " n_jobs=-1)\n", + "models = {\n", + " \"t-SNE\": tsne,\n", + " \"UMAP\": umap_reducer,\n", + " \"Isomap\": isomap,\n", + " \"KernelPCA (RBF)\": kpca\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6cef4b2c", + "metadata": {}, + "outputs": [], + "source": [ + "fig, axes = plt.subplots(2, 4, figsize=(20, 10))\n", + "fig.suptitle(\n", + " \"Визуализация понижения размерности CIFAR-10 ({} сэмплов)\".format(n_samples),\n", + " fontsize=16,\n", + ")\n", + "for i, (name, model) in enumerate(models.items()):\n", + "\n", + " if name == \"t-SNE\":\n", + " X_train_reduced = model.fit_transform(X_train_sample)\n", + " else:\n", + " model.fit(X_train_sample)\n", + " X_train_reduced = model.transform(X_train_sample)\n", + "\n", + " plot_2d_data(\n", + " X_train_reduced, y_train_sample, title=f\"{name} (Train)\", ax=axes[0, i]\n", + " )\n", + "\n", + " if name == \"t-SNE\":\n", + " axes[1, i].set_title(f\"{name} (Test - N/A)\")\n", + " axes[1, i].set_xticks([])\n", + " axes[1, i].set_yticks([])\n", + " axes[1, i].text(\n", + " 0.5,\n", + " 0.5,\n", + " \"N/A for sklearn t-SNE\",\n", + " horizontalalignment=\"center\",\n", + " verticalalignment=\"center\",\n", + " transform=axes[1, i].transAxes,\n", + " color=\"gray\",\n", + " )\n", + " else:\n", + " X_test_reduced = model.transform(X_test_sample)\n", + " plot_2d_data(\n", + " X_test_reduced, y_test_sample, title=f\"{name} (Test)\", ax=axes[1, i]\n", + " )\n", + "\n", + "plt.tight_layout(rect=(0, 0.03, 1, 0.95))\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "fa21247b", + "metadata": { + "id": "fa21247b" + }, + "source": [ + "#### **Задание 2.2 [кросспроверка, 1 балл][вопрос]**\n", + "Опишите увиденное. Почему алгоритмы могли отработать не так, как вы ожидали?" + ] + }, + { + "cell_type": "markdown", + "id": "8bac15da", + "metadata": { + "id": "8bac15da" + }, + "source": [ + "**Ваш ответ здесь:** (o・_・)ノ”(ノ_<、):\n", + "\n", + "Кластеры на графиках получились слабо разделёнными. График t-SNE для тестовых данных пуст, потому что стандартная реализация TSNE в scikit-learn не умеет отдельно применять уже обученное преобразование к новым данным.\n", + "\n", + "Основные причины плохого результата: данные CIFAR-10 имеют очень высокую размерность — 3072 признака, и при сжатии до 2 измерений теряется много важной информации. Кроме того, качество визуализации сильно зависит от выбранных гиперпараметров, которые могли быть неудачными для этой задачи.\n" + ] + }, + { + "cell_type": "markdown", + "id": "59a23068", + "metadata": { + "id": "59a23068" + }, + "source": [ + "#### **Задание 2.3 [кросспроверка, 1 балл][вопрос]**\n", + "Методы снижения размерности, как и другие метрические методы испытывают трудности при работе с данными высокой размерности. Напишите как минимум две причины, почему." + ] + }, + { + "cell_type": "markdown", + "id": "cb1d9444", + "metadata": { + "id": "cb1d9444" + }, + "source": [ + "**Ваш ответ здесь:** (o・_・)ノ”(ノ_<、):\n", + "\n", + "В высоких размерностях метрические методы работают хуже из-за «проклятия размерности». Расстояния между точками становятся почти одинаковыми, поэтому понятие ближайшего соседа теряет смысл.\n", + "\n", + "Кроме того, большое число шумовых или нерелевантных признаков может скрывать влияние действительно важных признаков. При росте размерности пространство быстро становится разреженным, точки оказываются далеко друг от друга, а локальные окрестности перестают быть информативными.\n" + ] + }, + { + "cell_type": "markdown", + "id": "6c059cf2", + "metadata": { + "id": "6c059cf2" + }, + "source": [ + "Один из способов решения этих проблем — перейти в другое, более репрезентативное пространство признаков, где объекты будут расположены в многообразии, которое легче представить в двумерном пространстве. Чтобы выполнить такое преобразование воспользуемся типичным подходом **Transfer Learning** — предобученными нейронными сетями. С помощью глубокой сети обученной на другом наборе изображений (`ImageNet`) мы перейдём в новое векторное пространство и затем применим методы снижения размерности." + ] + }, + { + "cell_type": "markdown", + "id": "a4811aa2", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T23:11:12.954110Z", + "start_time": "2023-03-01T23:11:12.949227Z" + }, + "id": "a4811aa2" + }, + "source": [ + "Так как локальный подсчёт эмбеддингов изображений может занять много времени, Вы можете попробовать скачать их c помощью `gdown`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99c42afa", + "metadata": { + "deletable": false, + "editable": false, + "id": "99c42afa", + "run_control": { + "frozen": true + } + }, + "outputs": [], + "source": [ + "gdown.download(id='16UgWo1Emt9ar1O4h2Xxed0ZpJZ0OG5V-', output='cifar10_deep_features.npy')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14d31486", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T15:03:18.550553Z", + "start_time": "2023-03-01T15:03:18.414991Z" + }, + "id": "14d31486" + }, + "outputs": [], + "source": [ + "FEATURES_PATH = './cifar10_deep_features.npy'\n", + "\n", + "if not os.path.exists(FEATURES_PATH):\n", + " deep_cnn = InceptionV3(weights='imagenet', include_top=False, input_shape=(139, 139, 3))\n", + "\n", + " cifar10_tensors_test = torch.nn.functional.interpolate(torch.tensor(\n", + " cifar10_images_test.transpose(0, 3, 1, 2)\n", + " ), size=139).numpy().transpose(0, 2, 3, 1).astype(np.float32)\n", + " cifar10_tensors_train = torch.nn.functional.interpolate(torch.tensor(\n", + " cifar10_images_train.transpose(0, 3, 1, 2)\n", + " ), size=139).numpy().transpose(0, 2, 3, 1).astype(np.float32)\n", + "\n", + " cifar10_deep_features_test = deep_cnn.predict(\n", + " preprocess_input(cifar10_tensors_test)\n", + " ).mean(axis=(1, 2)).reshape([cifar10_tensors_test.shape[0], -1])\n", + " cifar10_deep_features_train = deep_cnn.predict(\n", + " preprocess_input(cifar10_tensors_train)\n", + " ).mean(axis=(1, 2)).reshape([cifar10_tensors_train.shape[0], -1])\n", + "\n", + " np.save(FEATURES_PATH, [cifar10_deep_features_test, cifar10_deep_features_train])\n", + "else:\n", + " cifar10_deep_features_test, cifar10_deep_features_train = np.load(FEATURES_PATH, allow_pickle=True)" + ] + }, + { + "cell_type": "markdown", + "id": "4da40a6c", + "metadata": { + "id": "4da40a6c" + }, + "source": [ + "#### **Задание 2.4 [кросспроверка, 2 баллa][код]**\n", + "Используйте выделенные признаки для обучения алгоритмов из предыдущего пункта. Постройте графики. Замечание из пункта [**2.1**](#task_2.1) остаётся в силе." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52f01d2e", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T15:03:24.502561Z", + "start_time": "2023-03-01T15:03:24.438443Z" + }, + "id": "52f01d2e" + }, + "outputs": [], + "source": [ + "# Ваш код здесь:\(º □ º l|l)/\n", + "X_train_deep_sample = cifar10_deep_features_train[train_indices]\n", + "y_train_sample = cifar10_labels_train[train_indices]\n", + "X_test_deep_sample = cifar10_deep_features_test[test_indices]\n", + "y_test_sample = cifar10_labels_test[test_indices]\n", + "\n", + "scaler_deep = StandardScaler()\n", + "X_train_deep_scaled = scaler_deep.fit_transform(X_train_deep_sample)\n", + "X_test_deep_scaled = scaler_deep.transform(X_test_deep_sample)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ccbed545", + "metadata": {}, + "outputs": [], + "source": [ + "fig_deep, axes_deep = plt.subplots(2, 4, figsize=(20, 10))\n", + "fig_deep.suptitle(\n", + " \"Визуализация понижения размерности CIFAR-10 (Глубокие признаки, {} сэмплов)\".format(\n", + " n_samples\n", + " ),\n", + " fontsize=16,\n", + ")\n", + "\n", + "for i, (name, model) in enumerate(models.items()):\n", + " if name == \"t-SNE\":\n", + " X_train_deep_reduced = model.fit_transform(X_train_deep_scaled)\n", + " else:\n", + " model.fit(X_train_deep_scaled)\n", + " X_train_deep_reduced = model.transform(X_train_deep_scaled)\n", + "\n", + " plot_2d_data(\n", + " X_train_deep_reduced,\n", + " y_train_sample,\n", + " title=f\"{name} (Train - Deep Feat.)\",\n", + " ax=axes_deep[0, i],\n", + " )\n", + "\n", + " if name == \"t-SNE\":\n", + " axes_deep[1, i].set_title(f\"{name} (Test - N/A)\")\n", + " axes_deep[1, i].set_xticks([])\n", + " axes_deep[1, i].set_yticks([])\n", + " axes_deep[1, i].text(\n", + " 0.5,\n", + " 0.5,\n", + " \"N/A for sklearn t-SNE\",\n", + " horizontalalignment=\"center\",\n", + " verticalalignment=\"center\",\n", + " transform=axes_deep[1, i].transAxes,\n", + " color=\"gray\",\n", + " )\n", + " else:\n", + " X_test_deep_reduced = model.transform(X_test_deep_scaled)\n", + " plot_2d_data(\n", + " X_test_deep_reduced,\n", + " y_test_sample,\n", + " title=f\"{name} (Test - Deep Feat.)\",\n", + " ax=axes_deep[1, i],\n", + " )\n", + "\n", + "plt.tight_layout(rect=(0, 0.03, 1, 0.95))\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "f47774e2", + "metadata": { + "id": "f47774e2" + }, + "source": [ + "#### **Задание 2.5 [кросспроверка, 1 балл][вопрос]**\n", + "1. Есть ли какие-то изменения по сравнению с использованием исходных признаков?\n", + "2. Как вы думаете, почему использование глубоких признаков помогло/не помогло в задаче снижения размерности?\n", + "3. Какой алгоритм показал себя лучше на ваш взгляд?\n", + "4. Согласованы ли преобразования на обучающей и тестовых выборках? Какие недостатки есть в том, что преобразование на тестовой выборке выглядит отлично от низкоразмерного представления обучающей выборки?\n", + "5. Какие из алгоритмов можно использовать в качестве первого шага по снижению размерности в задачах машинного обучения? Какой из них использовали бы вы?" + ] + }, + { + "cell_type": "markdown", + "id": "d5c7c172", + "metadata": { + "id": "d5c7c172" + }, + "source": [ + "**Ваш ответ здесь:** (o・_・)ノ”(ノ_<、):\n", + "\n", + "Да, различия стали заметны: на глубоких признаках кластеры, особенно у t-SNE и UMAP, выглядят намного более чёткими. Точки одного класса группируются плотнее и лучше отделяются от остальных.\n", + "\n", + "Глубокие признаки помогли, потому что нейросети выделяют не сырые пиксели, а более содержательные характеристики изображений: формы, текстуры и части объектов. Поэтому изображения одного класса оказываются ближе друг к другу. Также такие признаки устойчивее к сдвигам, масштабу и изменению освещения.\n", + "\n", + "Лучше всего показал себя UMAP: его кластеры выглядят наиболее компактными и хорошо разделёнными.\n", + "\n", + "Преобразования для обучающей и тестовой выборок выглядят согласованными: структура кластеров и их расположение похожи. Это важно, потому что при сильных различиях нельзя было бы доверять положению новых точек на 2D-графике, а сама модель могла бы быть переобучена.\n", + "\n", + "Из всех алгоритмов я бы выбрал UMAP, так как он даёт хорошее разделение классов и работает достаточно быстро. Его удобно использовать для предварительной обработки данных перед применением ML-алгоритмов.\n" + ] + }, + { + "cell_type": "markdown", + "id": "c8a1410e", + "metadata": { + "id": "c8a1410e" + }, + "source": [ + "Далее, для визуализации кластеризации используйте один из методов снижения размерности на ваш выбор и то векторное представление, которое лучше всего себя проявило (исходное или полученное с помощью глубокой сети). Кластеризацию обучайте также на наиболее подходящем высокоразмерном векторном представлении." + ] + }, + { + "cell_type": "markdown", + "id": "63e9c886", + "metadata": { + "id": "63e9c886" + }, + "source": [ + "#### **Задание 2.6 [кросспроверка, 1 балл][код, вопрос]**\n", + "Изобразите выборку CIFAR10 с помощью выбранного алгоритма снижения размерности.\n", + "\n", + "**Совет** Изобразите результат с помощью `plot_interactive`, чтобы изучить особенности кластеризации в соответствии с исходными изображениями. Если вы нашли интересные особенности — напишите про это." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ea28424", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T15:07:48.895654Z", + "start_time": "2023-03-01T15:07:48.812018Z" + }, + "id": "3ea28424" + }, + "outputs": [], + "source": [ + "%matplotlib ipympl\n", + "matplotlib.rcParams['figure.dpi'] = 300\n", + "\n", + "# Для работы в Google Colab нужно выполнить специфичную магию\n", + "# Обычно, она не срабатывает с первого раза, поэтому может потребоваться\n", + "# несколько раз выполнить ячейку и несколько раз попробовать нарисовать график\n", + "try:\n", + " from google.colab import output\n", + " output.enable_custom_widget_manager()\n", + "except:\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ffb1d8c8", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T15:07:52.786201Z", + "start_time": "2023-03-01T15:07:52.703184Z" + }, + "id": "ffb1d8c8" + }, + "outputs": [], + "source": [ + "# Ваш код здесь:\(º □ º l|l)/\n", + "plot_interactive(\n", + " umap_reducer.transform(X_train_deep_scaled),\n", + " cifar10_images_train,\n", + " y_train_sample,\n", + " cifar10_test_dataset.classes,\n", + " n_dots=2000,\n", + " image_scale=0.35,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "0c0917b7", + "metadata": { + "id": "0c0917b7" + }, + "source": [ + "Вернёмся в статичный режим отрисовки изображений:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2f18f1bf", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T15:09:28.554080Z", + "start_time": "2023-03-01T15:09:28.469693Z" + }, + "id": "2f18f1bf" + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "matplotlib.rcParams['figure.dpi'] = 300" + ] + }, + { + "cell_type": "markdown", + "id": "2568e573", + "metadata": { + "id": "2568e573" + }, + "source": [ + "Теперь, когда мы можем визуализировать кластеризацию, можно сравнить алгоритмы из первой части на естественных данных." + ] + }, + { + "cell_type": "markdown", + "id": "ccd94f6a", + "metadata": { + "id": "ccd94f6a" + }, + "source": [ + "#### **Задание 2.7 [кросспроверка, 1.5 балла][код]**\n", + "Подберите параметры `KMeans`, `DBSCAN`, `AgglomerativeClustering` используя силуэт и B-Cubed. Визуализируйте получившиеся кластеризации также, как и в задании **1.с.4** в ноутбуке [Base] Clusterizartion. Для ускорения перебора можете производить его на небольшой доле от всех объектов ($1000\\text{-}2000$ объектов).\n", + "\n", + "*Замечание:* Алгоритмы кластеризации нужно применять к исходному векторному представлению. Снижение размерности используется только для визуализации." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc34b6a9", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T15:09:31.321473Z", + "start_time": "2023-03-01T15:09:31.238779Z" + }, + "id": "cc34b6a9" + }, + "outputs": [], + "source": [ + "n_objects = 2000" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e1debbc8", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T15:09:32.490742Z", + "start_time": "2023-03-01T15:09:32.411260Z" + }, + "id": "e1debbc8" + }, + "outputs": [], + "source": [ + "# Ваш код здесь:\(º □ º l|l)/\n", + "from matplotlib.ticker import MaxNLocator\n", + "\n", + "\n", + "def heatmap(\n", + " data, row_labels, col_labels, ax=None, cbar_kw=None, cbarlabel=\"\", **kwargs\n", + "):\n", + " if ax is None:\n", + " ax = plt.gca()\n", + " if cbar_kw is None:\n", + " cbar_kw = {}\n", + " im = ax.imshow(data, **kwargs)\n", + " cbar = ax.figure.colorbar(im, ax=ax, **cbar_kw)\n", + " cbar.ax.set_ylabel(cbarlabel, rotation=-90, va=\"bottom\")\n", + " ax.set_xticks(np.arange(data.shape[1]), labels=col_labels)\n", + " ax.set_yticks(np.arange(data.shape[0]), labels=row_labels)\n", + " ax.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False)\n", + " plt.setp(ax.get_xticklabels(), rotation=-30, ha=\"right\", rotation_mode=\"anchor\")\n", + " for edge, spine in ax.spines.items():\n", + " spine.set_visible(False)\n", + " ax.set_xticks(np.arange(data.shape[1] + 1) - 0.5, minor=True)\n", + " ax.set_yticks(np.arange(data.shape[0] + 1) - 0.5, minor=True)\n", + " ax.grid(which=\"minor\", color=\"w\", linestyle=\"-\", linewidth=3)\n", + " ax.tick_params(which=\"minor\", bottom=False, left=False)\n", + " return im, cbar\n", + "\n", + "\n", + "def annotate_heatmap(\n", + " im,\n", + " data=None,\n", + " valfmt=\"{x:.2f}\",\n", + " textcolors=(\"black\", \"white\"),\n", + " threshold=None,\n", + " **textkw\n", + "):\n", + " if not isinstance(data, (list, np.ndarray)):\n", + " data = im.get_array()\n", + " if threshold is not None:\n", + " threshold = im.norm(threshold)\n", + " else:\n", + " threshold = im.norm(data.max()) / 2.0\n", + " kw = dict(horizontalalignment=\"center\", verticalalignment=\"center\")\n", + " kw.update(textkw)\n", + " if isinstance(valfmt, str):\n", + " valfmt = plt.matplotlib.ticker.StrMethodFormatter(valfmt)\n", + " texts = []\n", + " for i in range(data.shape[0]):\n", + " for j in range(data.shape[1]):\n", + " kw.update(color=textcolors[int(im.norm(data[i, j]) > threshold)])\n", + " text = im.axes.text(j, i, valfmt(data[i, j], None), **kw)\n", + " texts.append(text)\n", + " return texts" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "effd3bcf", + "metadata": {}, + "outputs": [], + "source": [ + "X_sample = cifar10_deep_features_train[train_indices]\n", + "y_sample = cifar10_labels_train[train_indices]\n", + "scaler = StandardScaler()\n", + "X_sample_scaled = scaler.fit_transform(X_sample)\n", + "X_sample_umap = umap_reducer.fit_transform(X_sample_scaled)\n", + "\n", + "k_range = list(range(1, 21))\n", + "kmeans_sil_scores = []\n", + "kmeans_bcubed_scores = []\n", + "\n", + "for k in k_range:\n", + " kmeans = KMeans(n_clusters=k, n_init=\"auto\")\n", + " labels = kmeans.fit_predict(X_sample_scaled)\n", + " sil = silhouette_score(X_sample_scaled, labels)\n", + " bcubed = bcubed_score(y_sample, labels)\n", + " kmeans_sil_scores.append(sil)\n", + " kmeans_bcubed_scores.append(bcubed)\n", + "\n", + "best_k_sil = k_range[np.argmax(kmeans_sil_scores)]\n", + "best_k_bcubed = k_range[np.argmax(kmeans_bcubed_scores)]\n", + "print(f\"Best k (Silhouette): {best_k_sil} (Score: {max(kmeans_sil_scores):.4f})\")\n", + "print(f\"Best k (B-Cubed): {best_k_bcubed} (Score: {max(kmeans_bcubed_scores):.4f})\")\n", + "\n", + "kmeans_best_sil = KMeans(n_clusters=best_k_sil, n_init=\"auto\")\n", + "labels_best_sil_km = kmeans_best_sil.fit_predict(X_sample_scaled)\n", + "\n", + "kmeans_best_bcubed = KMeans(n_clusters=best_k_bcubed, n_init=\"auto\")\n", + "labels_best_bcubed_km = kmeans_best_bcubed.fit_predict(X_sample_scaled)\n", + "\n", + "fig_km, axes_km = plt.subplots(2, 2, figsize=(12, 10)) # Увеличим размер фигуры\n", + "fig_km.suptitle(\"KMeans Clustering Performance (Deep Features)\", fontsize=14)\n", + "\n", + "axes_km[0, 0].plot(k_range, kmeans_sil_scores, marker=\"o\")\n", + "axes_km[0, 0].set_xlabel(\"Number of clusters (k)\")\n", + "axes_km[0, 0].set_ylabel(\"Silhouette Score\")\n", + "axes_km[0, 0].set_title(\"Silhouette vs. k\")\n", + "axes_km[0, 0].axvline(\n", + " best_k_sil, color=\"r\", linestyle=\"--\", label=f\"Best k = {best_k_sil}\"\n", + ")\n", + "axes_km[0, 0].xaxis.set_major_locator(MaxNLocator(integer=True))\n", + "axes_km[0, 0].grid(True)\n", + "axes_km[0, 0].legend()\n", + "\n", + "axes_km[0, 1].plot(k_range, kmeans_bcubed_scores, marker=\"o\")\n", + "axes_km[0, 1].set_xlabel(\"Number of clusters (k)\")\n", + "axes_km[0, 1].set_ylabel(\"B-Cubed F1 Score\")\n", + "axes_km[0, 1].set_title(\"B-Cubed F1 vs. k\")\n", + "axes_km[0, 1].axvline(\n", + " best_k_bcubed, color=\"r\", linestyle=\"--\", label=f\"Best k = {best_k_bcubed}\"\n", + ")\n", + "axes_km[0, 1].xaxis.set_major_locator(MaxNLocator(integer=True))\n", + "axes_km[0, 1].grid(True)\n", + "axes_km[0, 1].legend()\n", + "\n", + "plot_2d_data(\n", + " X_sample_umap,\n", + " labels_best_sil_km,\n", + " title=f\"KMeans (k={best_k_sil}, Best Silhouette)\",\n", + " ax=axes_km[1, 0],\n", + ")\n", + "plot_2d_data(\n", + " X_sample_umap,\n", + " labels_best_bcubed_km,\n", + " title=f\"KMeans (k={best_k_bcubed}, Best B-Cubed)\",\n", + " ax=axes_km[1, 1],\n", + ")\n", + "\n", + "plt.tight_layout(rect=[0, 0.03, 1, 0.95])\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d7af531", + "metadata": {}, + "outputs": [], + "source": [ + "def plot_2d_data_new(data, labels, title=\"Исходные данные\", cmap=\"tab20\", ax=None):\n", + " unique_labels = np.unique(labels)\n", + " n_unique_labels_total = len(unique_labels)\n", + " do_show = False\n", + " if ax is None:\n", + " fig, ax = plt.subplots(1, 1, figsize=(8, 6))\n", + " do_show = True\n", + "\n", + " is_noise = -1 in unique_labels\n", + " valid_labels = unique_labels[unique_labels != -1]\n", + " n_clusters = len(valid_labels)\n", + "\n", + " if is_noise:\n", + " noise_mask = labels == -1\n", + " cluster_mask = ~noise_mask\n", + " else:\n", + " cluster_mask = np.ones(len(labels), dtype=bool)\n", + " noise_mask = np.zeros(len(labels), dtype=bool)\n", + "\n", + " if n_clusters > 0:\n", + " cmap_instance = plt.get_cmap(cmap, n_clusters if n_clusters > 1 else 2)\n", + " else:\n", + " cmap_instance = None\n", + "\n", + " if np.sum(cluster_mask) > 0 and cmap_instance is not None:\n", + " scatter = ax.scatter(\n", + " data[cluster_mask, 0],\n", + " data[cluster_mask, 1],\n", + " c=labels[cluster_mask],\n", + " cmap=cmap_instance,\n", + " s=20,\n", + " alpha=0.9,\n", + " )\n", + "\n", + " if n_clusters > 0:\n", + " cbar = plt.colorbar(scatter, label=\"Номер кластера\", ax=ax)\n", + " max_ticks = 15\n", + " if n_clusters > max_ticks:\n", + " cbar.ax.yaxis.set_major_locator(\n", + " MaxNLocator(integer=True, nbins=max_ticks)\n", + " )\n", + " else:\n", + " if len(valid_labels) > 0:\n", + " ticks = np.arange(min(valid_labels), max(valid_labels) + 1, 1)\n", + " cbar.set_ticks(ticks)\n", + "\n", + " if np.sum(noise_mask) > 0:\n", + " ax.scatter(\n", + " data[noise_mask, 0],\n", + " data[noise_mask, 1],\n", + " c=\"gray\",\n", + " marker=\"x\",\n", + " s=15,\n", + " alpha=0.5,\n", + " label=f\"Шум ({np.sum(noise_mask)} точек)\",\n", + " )\n", + " ax.legend(loc=\"best\", fontsize=\"small\")\n", + "\n", + " ax.set_title(f\"{title}\\n(Найдено кластеров: {n_clusters})\", fontsize=10)\n", + " ax.set_xticks([])\n", + " ax.set_yticks([])\n", + " ax.grid(True, linestyle=\"--\", alpha=0.5)\n", + "\n", + " if do_show:\n", + " plt.tight_layout()\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aab17212", + "metadata": {}, + "outputs": [], + "source": [ + "eps_range = np.linspace(15, 60, 5)\n", + "min_samples_range = list(range(1, 101, 10))\n", + "\n", + "dbscan_sil_scores = np.zeros((len(eps_range), len(min_samples_range)))\n", + "dbscan_bcubed_scores = np.zeros((len(eps_range), len(min_samples_range)))\n", + "dbscan_n_clusters = np.zeros((len(eps_range), len(min_samples_range)), dtype=int)\n", + "\n", + "for i, eps in enumerate(eps_range):\n", + " for j, ms in enumerate(min_samples_range):\n", + " dbscan = DBSCAN(eps=eps, min_samples=ms, n_jobs=-1)\n", + " labels = dbscan.fit_predict(X_sample_scaled)\n", + "\n", + " unique_labels = np.unique(labels)\n", + " n_clusters = len(unique_labels[unique_labels != -1])\n", + " dbscan_n_clusters[i, j] = n_clusters\n", + "\n", + " mask_non_noise = labels != -1\n", + " if np.sum(mask_non_noise) > 1 and len(np.unique(labels[mask_non_noise])) > 1:\n", + " sil = silhouette_score(\n", + " X_sample_scaled[mask_non_noise], labels[mask_non_noise]\n", + " )\n", + " else:\n", + " sil = 0.0\n", + "\n", + " bcubed = bcubed_score(y_sample, labels)\n", + "\n", + " dbscan_sil_scores[i, j] = sil\n", + " dbscan_bcubed_scores[i, j] = bcubed\n", + "\n", + "sil_scores_valid = np.copy(dbscan_sil_scores)\n", + "sil_scores_valid[sil_scores_valid <= 0] = -np.inf\n", + "best_idx_sil_db = np.unravel_index(\n", + " np.argmax(sil_scores_valid, axis=None), sil_scores_valid.shape\n", + ")\n", + "\n", + "best_idx_bcubed_db = np.unravel_index(\n", + " np.argmax(dbscan_bcubed_scores, axis=None), dbscan_bcubed_scores.shape\n", + ")\n", + "\n", + "best_eps_sil = eps_range[best_idx_sil_db[0]]\n", + "best_ms_sil = min_samples_range[best_idx_sil_db[1]]\n", + "print(\n", + " f\"Best params (Silhouette): eps={best_eps_sil:.2f}, ms={best_ms_sil} (Score: {dbscan_sil_scores[best_idx_sil_db]:.4f})\"\n", + ")\n", + "\n", + "best_eps_bcubed = eps_range[best_idx_bcubed_db[0]]\n", + "best_ms_bcubed = min_samples_range[best_idx_bcubed_db[1]]\n", + "print(\n", + " f\"Best params (B-Cubed): eps={best_eps_bcubed:.2f}, ms={best_ms_bcubed} (Score: {dbscan_bcubed_scores[best_idx_bcubed_db]:.4f})\"\n", + ")\n", + "\n", + "dbscan_best_sil = DBSCAN(eps=best_eps_sil, min_samples=best_ms_sil, n_jobs=-1)\n", + "labels_best_sil_db = dbscan_best_sil.fit_predict(X_sample_scaled)\n", + "\n", + "dbscan_best_bcubed = DBSCAN(eps=best_eps_bcubed, min_samples=best_ms_bcubed, n_jobs=-1)\n", + "labels_best_bcubed_db = dbscan_best_bcubed.fit_predict(X_sample_scaled)\n", + "\n", + "fig_db, axes_db = plt.subplots(2, 2, figsize=(14, 12))\n", + "fig_db.suptitle(\"DBSCAN Clustering Performance (Deep Features)\", fontsize=14)\n", + "\n", + "im_sil, _ = heatmap(\n", + " dbscan_sil_scores,\n", + " [f\"{e:.1f}\" for e in eps_range],\n", + " [str(ms) for ms in min_samples_range],\n", + " ax=axes_db[0, 0],\n", + " cmap=\"viridis\",\n", + " cbarlabel=\"Silhouette Score\",\n", + ")\n", + "annotate_heatmap(im_sil, valfmt=\"{x:.2f}\")\n", + "axes_db[0, 0].set_title(\"Silhouette Score\")\n", + "axes_db[0, 0].set_ylabel(\"eps\")\n", + "axes_db[0, 0].add_patch(\n", + " plt.Rectangle(\n", + " (best_idx_sil_db[1] - 0.5, best_idx_sil_db[0] - 0.5),\n", + " 1,\n", + " 1,\n", + " fill=False,\n", + " edgecolor=\"red\",\n", + " lw=2,\n", + " )\n", + ")\n", + "\n", + "\n", + "im_bc, _ = heatmap(\n", + " dbscan_bcubed_scores,\n", + " [f\"{e:.1f}\" for e in eps_range],\n", + " [str(ms) for ms in min_samples_range],\n", + " ax=axes_db[0, 1],\n", + " cmap=\"viridis\",\n", + " cbarlabel=\"B-Cubed F1 Score\",\n", + ")\n", + "annotate_heatmap(im_bc, valfmt=\"{x:.2f}\")\n", + "axes_db[0, 1].set_title(\"B-Cubed F1 Score\")\n", + "axes_db[0, 1].set_ylabel(\"eps\")\n", + "axes_db[0, 1].add_patch(\n", + " plt.Rectangle(\n", + " (best_idx_bcubed_db[1] - 0.5, best_idx_bcubed_db[0] - 0.5),\n", + " 1,\n", + " 1,\n", + " fill=False,\n", + " edgecolor=\"red\",\n", + " lw=2,\n", + " )\n", + ")\n", + "\n", + "plot_2d_data_new(\n", + " X_sample_umap,\n", + " labels_best_sil_db,\n", + " title=f\"DBSCAN (eps={best_eps_sil:.1f}, ms={best_ms_sil}, Best Sil.)\",\n", + " ax=axes_db[1, 0],\n", + ")\n", + "plot_2d_data_new(\n", + " X_sample_umap,\n", + " labels_best_bcubed_db,\n", + " title=f\"DBSCAN (eps={best_eps_bcubed:.1f}, ms={best_ms_bcubed}, Best B-Cubed)\",\n", + " ax=axes_db[1, 1],\n", + ")\n", + "\n", + "plt.tight_layout(rect=(0, 0.03, 1, 0.95))\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "7bb549d8", + "metadata": { + "id": "7bb549d8" + }, + "source": [ + "#### **Задание 2.8 [кросспроверка, 1 балл][вопрос]** \n", + "1. Какие алгоритмы справились с кластеризацией естественных данных?\n", + "2. Получилось ли подобрать оптимальное число кластеров с помощью BCubed и коэффициента силуэта?\n", + "3. Объясните почему коэффициент силуэта не позволил выполнить подбор оптимальных гиперпараметров." + ] + }, + { + "cell_type": "markdown", + "id": "0f00d2ec", + "metadata": { + "id": "0f00d2ec" + }, + "source": [ + "**Ваш ответ здесь:** (o・_・)ノ”(ノ_<、):\n", + "\n", + "KMeans и Agglomerative Clustering показали адекватные результаты по метрике B-Cubed: оба алгоритма смогли частично выявить структуру данных. На визуализациях для выбранных значений k видно более осмысленное разбиение на несколько групп, чем при k=2, хотя кластеры всё ещё заметно перекрываются.\n", + "\n", + "Подобрать число кластеров удалось по метрике B-Cubed. Коэффициент силуэта, наоборот, не дал корректного результата для разбиения на реальные классы.\n", + "\n", + "Силуэт плохо подходит для этой задачи, потому что это внутренняя метрика, основанная на евклидовых расстояниях. Она лучше работает для плотных, хорошо разделённых и примерно сферических кластеров. В случае CIFAR-10 кластеры на UMAP-визуализациях пересекаются, имеют сложную форму и не являются идеально отделёнными, поэтому силуэт даёт менее полезную оценку.\n" + ] + }, + { + "cell_type": "markdown", + "id": "6f8bd1c1", + "metadata": { + "id": "6f8bd1c1" + }, + "source": [ + "Интересный способ визуализации Иерархической кластеризации — построение дендрограммы. Такой способ визуализации позволяет анализировать, как именно связаны между собой объекты, подбирать оптимальное число кластеров, а также определять, какие классы отделяются \"хорошо\" от других классов, а какие классы перемешаны в одном кластере." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13af91a1", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T15:43:08.779419Z", + "start_time": "2023-03-01T15:43:08.693099Z" + }, + "id": "13af91a1" + }, + "outputs": [], + "source": [ + "def plot_dendrogram(model, labels, classes, ax):\n", + " n_classes = len(classes)\n", + " n_samples = len(model.labels_)\n", + " n_u_connections = model.children_.shape[0]\n", + " colors = plt.get_cmap('tab20', n_classes).colors\n", + "\n", + " bin_counts = np.zeros([n_u_connections, n_classes])\n", + " for i, merge in enumerate(model.children_):\n", + " current_bin_count = np.zeros(n_classes)\n", + " for child_idx in merge:\n", + " if child_idx < n_samples:\n", + " current_bin_count[labels[child_idx]] += 1\n", + " else:\n", + " current_bin_count += bin_counts[child_idx - n_samples]\n", + "\n", + " bin_counts[i] = current_bin_count\n", + "\n", + " linkage_matrix = np.column_stack(\n", + " [model.children_, model.distances_, np.sum(bin_counts, axis=1)]\n", + " ).astype(float)\n", + "\n", + " def leaf_label_func(idx):\n", + " if idx < len(labels):\n", + " return None\n", + " else:\n", + " ratio = 100 * np.max(bin_counts[idx - n_samples]) / np.sum(bin_counts[idx - n_samples])\n", + " if ratio < 100:\n", + " return '{0:.0f}%'.format(ratio)\n", + " else:\n", + " return None\n", + "\n", + " def link_color_func(idx):\n", + " mode_class = np.argmax(bin_counts[idx - n_samples])\n", + " return matplotlib.colors.to_hex(colors[mode_class], keep_alpha=True)\n", + "\n", + " scipy.cluster.hierarchy.dendrogram(\n", + " linkage_matrix, ax=ax, link_color_func=link_color_func, leaf_label_func=leaf_label_func,\n", + " orientation='right', truncate_mode=\"level\", p=9\n", + " )\n", + "\n", + " for idx, class_name in enumerate(classes):\n", + " ax.plot([], [], c=matplotlib.colors.to_hex(colors[idx], keep_alpha=True), label=class_name)\n", + " ax.legend()\n", + "\n", + " # Удалим накладывающиеся метки\n", + " threshold = 55\n", + " prev_position = -(threshold + 1)\n", + "\n", + " y_labels = ax.get_yaxis().get_ticklabels()\n", + " for label in y_labels:\n", + " if label.get_text() == '':\n", + " continue\n", + "\n", + " _, position = label.get_position()\n", + " if position - prev_position < threshold:\n", + " label.set_text('')\n", + " else:\n", + " prev_position = position\n", + " ax.get_yaxis().set_ticklabels(y_labels)\n", + "\n", + " ax.set_xlabel('Расстояние между кластерами')\n", + " ax.set_ylabel('Доля объектов наибольшего класса в данном кластере')\n", + "\n", + " ax.set_title('Дендрограмма Иерархической Кластеризации')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ac4622c", + "metadata": { + "ExecuteTime": { + "end_time": "2023-03-01T15:44:13.974036Z", + "start_time": "2023-03-01T15:44:07.940015Z" + }, + "id": "4ac4622c", + "scrolled": false + }, + "outputs": [], + "source": [ + "n_objects = 2000\n", + "model = AgglomerativeClustering(\n", + " n_clusters=None, distance_threshold=0.0, compute_distances=True, compute_full_tree=True\n", + ")\n", + "model = model.fit(cifar10_deep_features_train[:n_objects])\n", + "\n", + "fig, ax = plt.subplots(1, 1, figsize=(12, 12))\n", + "\n", + "plot_dendrogram(\n", + " model,\n", + " labels=cifar10_labels_train[:n_objects],\n", + " classes=cifar10_train_dataset.classes,\n", + " ax=ax\n", + ")\n", + "\n", + "fig.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "36866d6f", + "metadata": { + "id": "36866d6f" + }, + "source": [ + "#### **Задание 2.9 [кросспроверка, 0.5 балла][вопрос]** \n", + "\n", + "Проанализируйте получившуюся дендрограмму. Напишите свои наблюдения ниже." + ] + }, + { + "cell_type": "markdown", + "id": "07e76533", + "metadata": { + "id": "07e76533" + }, + "source": [ + "**Ваш ответ здесь:** (o・_・)ノ”(ノ_<、):\n", + "\n", + "На малых расстояниях происходит много ранних слияний, причём часто объединяются объекты одного истинного класса. Это видно по множеству одноцветных линий в левой части дендрограммы.\n", + "\n", + "Некоторые классы образуют крупные и относительно чистые ветви, например automobile, bird, truck и airplane. Это говорит о том, что они достаточно хорошо отделяются в пространстве признаков.\n", + "\n", + "Дендрограмма показывает иерархическую структуру: сначала объединяются наиболее похожие объекты, а затем небольшие чистые группы постепенно сливаются в более крупные, но менее однородные кластеры.\n", + "\n", + "При этом даже в доминирующих по цвету ветвях встречаются линии других классов, поэтому кластеры нельзя считать полностью чистыми: присутствует смешение объектов разных категорий.\n" + ] + } + ], + "metadata": { + "colab": { + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": ".venv (3.14.5)", + "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.14.5" + }, + "toc": { + "base_numbering": "0", + "nav_menu": { + "height": "317px", + "width": "260px" + }, + "number_sections": false, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "524.87px" + }, + "toc_section_display": true, + "toc_window_display": true + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/task31/requirements_2025_26_for_colab_small.txt b/task31/requirements_2025_26_for_colab_small.txt new file mode 100644 index 0000000..02d3fee --- /dev/null +++ b/task31/requirements_2025_26_for_colab_small.txt @@ -0,0 +1,22 @@ +catboost +gdown==5.2.0 +h5py==3.14.0 +hyperopt==0.2.7 +ipympl==0.9.7 +ipywidgets==7.7.1 +lightgbm==4.6.0 +matplotlib-inline==0.1.7 +matplotlib==3.10.0 +numpy +pandas +pep8==1.7.1 +plotly==5.24.1 +pycodestyle==2.14.0 +pytest==8.4.1 +scikit-image==0.25.2 +scikit-learn==1.6.1 +scipy==1.16.1 +seaborn==0.13.2 +tqdm==4.67.1 +umap-learn==0.5.9.post2 +xgboost==3.0.4 \ No newline at end of file