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