{
"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
}