Files
PracticeClassif/_research/SECTION_umap.md
2026-06-30 15:17:40 +03:00

267 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UMAP — кластеризация и визуализация признаков по классам
> Раздел методички. Научный руководитель — мнс Павленко Б.В.
> Задача-носитель: анализ эмбеддингов классификатора **EdgeNeXt** на датасете жестов **Naruto Sign**.
---
## 1. Что такое UMAP и интуиция
**UMAP** (Uniform Manifold Approximation and Projection) — алгоритм нелинейного снижения размерности, предложенный в [McInnes, Healy, Melville, 2018, arXiv:1802.03426](https://arxiv.org/abs/1802.03426). Теоретическая база — риманова геометрия и алгебраическая топология: предполагается, что данные лежат на многообразии (manifold) равномерно распределённой плотности, а локальная метрика искажается так, чтобы это допущение выполнялось.
**Интуиция (два шага):**
1. **Построение взвешенного графа соседства в высокой размерности.** Для каждой точки $x_i$ берутся её $k = $ `n_neighbors` ближайших соседей. Вес ребра — «вероятность» связи, спадающая с расстоянием. Локальный радиус нормируется на расстояние до 1-го соседа $\rho_i$ (так гарантируется связность) и масштаб $\sigma_i$ (калибруется так, чтобы сумма весов соответствовала $\log_2 k$):
$$
w_{i\to j} = \exp\!\left(-\frac{\max(0,\; d(x_i,x_j) - \rho_i)}{\sigma_i}\right)
$$
Ориентированные веса симметризуются (fuzzy-union): $w_{ij} = w_{i\to j} + w_{j\to i} - w_{i\to j}\,w_{j\to i}$. Получается «нечёткий топологический граф».
2. **Оптимизация низкоразмерного вложения.** Ищется расположение точек $y_i \in \mathbb{R}^{d}$ (`n_components`), при котором граф соседства в низкой размерности максимально похож на исходный. Минимизируется кросс-энтропия между нечёткими множествами рёбер; притяжение тянет соседей друг к другу, отталкивание (через negative sampling) разводит несоседей. Низкоразмерное ядро задаётся семейством $\frac{1}{1 + a\,\|y_i-y_j\|^{2b}}$, где $a,b$ подбираются под `min_dist`.
Инициализация по умолчанию — спектральная (Laplacian eigenmaps), что важно для сохранения глобальной структуры (см. ниже).
### Сравнение PCA / t-SNE / UMAP
| Критерий | PCA | t-SNE | UMAP |
|---|---|---|---|
| Тип | линейный | нелинейный | нелинейный (топологический) |
| Локальная структура | слабо | **очень хорошо** | **очень хорошо** |
| Глобальная структура | **хорошо** (дисперсия) | слабо | умеренно лучше t-SNE* |
| Сложность | $O(n d^2)$ | $\sim O(n^2)$ (Barnes-Hut $O(n\log n)$) | $\approx O(n^{1.14})$, на практике $O(n\log n)$ |
| Скорость на $10^5$$10^6$ точек | высокая | низкая | высокая |
| Стохастичность | детерминирован | стохастичен | стохастичен (нужен `random_state`) |
| Размерность выхода | любая | практично 23 | любая (хорошо масштабируется) |
| Интерпретируемость расстояний | оси = главные компоненты, расстояния значимы | **расстояния/размеры НЕ значимы** | **абсолютные расстояния/размеры НЕ значимы** |
| Глобальные оси | да | нет | нет |
\* Важная оговорка: преимущество UMAP по глобальной структуре во многом обусловлено спектральной инициализацией, а не самим алгоритмом оптимизации. При случайной инициализации UMAP сохраняет глобальную структуру не лучше t-SNE — см. [Kobak & Linderman, Nature Biotechnology 2021](https://www.nature.com/articles/s41587-020-00809-z) и интерактивный разбор [«Understanding UMAP», pair-code](https://pair-code.github.io/understanding-umap/).
**Практический вывод для нашей задачи:** PCA — быстрый baseline и шаг предобработки (денойзинг до 50100 компонент); t-SNE/UMAP — визуальный разведочный анализ; UMAP предпочтителен из-за скорости, масштабируемости и возможности давать промежуточные вложения для кластеризации.
---
## 2. Ключевые гиперпараметры UMAP
Определения и эффекты — по [официальной документации umap-learn (Basic Parameters)](https://umap-learn.readthedocs.io/en/latest/parameters.html).
| Параметр | По умолч. | Что контролирует | Эффект |
|---|---|---|---|
| `n_neighbors` | 15 | баланс локальное ↔ глобальное | малые (25) — тонкая локальная детализация, рвёт глобальную картину; большие (50200) — общая структура за счёт деталей |
| `min_dist` | 0.1 | минимальное расстояние между точками в выходе | малые (0.00.1) — плотные «комки», чёткие границы (для кластеризации); большие (0.50.99) — равномернее, для топологии/визуала |
| `n_components` | 2 | размерность вложения | 23 — визуализация; 1050 — препроцессинг под кластеризацию (UMAP хорошо масштабируется по размерности, в отличие от t-SNE) |
| `metric` | `euclidean` | метрика в исходном пространстве | для **L2-нормированных** эмбеддингов `cosine` эквивалентен углу; см. ниже |
| `random_state` | `None` | фиксация ГСЧ | задание делает результат воспроизводимым (но отключает параллелизм → медленнее) |
**`n_neighbors` (локальное vs глобальное).** Это число точек, формирующих локальную окрестность. Малое значение — UMAP «смотрит» только на ближайшее окружение; большое — усредняет по широкой области.
**`min_dist` (плотность кластеров).** Управляет ТОЛЬКО раскладкой в выходном пространстве (через $a,b$), не топологией графа. Для последующей кластеризации авторы рекомендуют `min_dist = 0.0`.
**`metric`.** Для эмбеддингов CNN после **L2-нормализации** косинусное расстояние монотонно связано с евклидовым:
$$
\|u - v\|_2^2 = 2 - 2\cos(u,v),\quad \text{при } \|u\|=\|v\|=1.
$$
Поэтому на нормированных признаках `metric='euclidean'` и `metric='cosine'` дают близкие, но не идентичные результаты (различие в нормировках весов и `n_neighbors`-калибровке). Рекомендация: для эмбеддингов брать `metric='cosine'` (или сначала L2-нормировать и оставить `euclidean`).
**`random_state`.** UMAP стохастичен (negative sampling, инициализация). Для отчётов/методички ОБЯЗАТЕЛЬНО фиксировать seed; форма «облака» при разных seed меняется, выводы — не должны.
---
## 3. Пайплайн: извлечение признаков EdgeNeXt → UMAP → раскраска по классам
Логика: `forward_features` → global pooling → L2-norm → матрица $[N, D]$ → UMAP → 2D scatter, цвет = истинный класс Naruto Sign.
```python
# deps: torch, timm, umap-learn, scikit-learn, matplotlib, numpy
import numpy as np
import torch
import torch.nn.functional as F
import timm
import umap
import matplotlib.pyplot as plt
device = "cuda" if torch.cuda.is_available() else "cpu"
# --- 1. EdgeNeXt feature extractor (pooled embedding, no classifier head) ---
# num_classes=0 -> timm returns pooled features from forward()
model = timm.create_model("edgenext_small", pretrained=True, num_classes=0)
model.eval().to(device)
@torch.no_grad()
def extract_embeddings(loader):
"""Return L2-normalized embeddings [N, D] and integer labels [N]."""
feats, labels = [], []
for imgs, ys in loader: # loader yields (image, class_idx)
imgs = imgs.to(device, non_blocking=True)
emb = model.forward_features(imgs) # spatial feature map [B, C, H, W]
emb = model.forward_head(emb, pre_logits=True) # pooled embedding [B, D]
emb = F.normalize(emb, p=2, dim=1) # L2-norm: cosine == euclidean geometry
feats.append(emb.cpu().numpy())
labels.append(ys.numpy())
return np.concatenate(feats), np.concatenate(labels)
X, y = extract_embeddings(val_loader) # X: [N, D], y: [N]
print("embeddings:", X.shape)
# --- 2. UMAP -> 2D for VISUALIZATION (seed fixed!) ---
reducer_2d = umap.UMAP(
n_neighbors=15,
min_dist=0.1,
n_components=2,
metric="cosine", # X already L2-normalized
random_state=42, # reproducibility
)
emb_2d = reducer_2d.fit_transform(X) # [N, 2]
# --- 3. Scatter colored by TRUE class ---
class_names = val_loader.dataset.classes # list[str], len == n_classes
fig, ax = plt.subplots(figsize=(9, 8))
sc = ax.scatter(emb_2d[:, 0], emb_2d[:, 1], c=y, cmap="tab20", s=8, alpha=0.7)
handles, _ = sc.legend_elements(num=len(class_names))
ax.legend(handles, class_names, title="Naruto Sign", loc="best",
fontsize=7, ncol=2, markerscale=1.5)
ax.set_title("EdgeNeXt embeddings — UMAP 2D (seed=42)")
ax.set_xlabel("UMAP-1"); ax.set_ylabel("UMAP-2")
ax.set_xticks([]); ax.set_yticks([]) # axes are not interpretable
plt.tight_layout()
plt.savefig("umap_edgenext_naruto.png", dpi=200)
```
> Примечание: точные имена методов извлечения признаков зависят от версии `timm`. Для EdgeNeXt подходит связка `forward_features` → `forward_head(..., pre_logits=True)`; альтернатива — `timm.create_model(..., num_classes=0, global_pool='avg')` и затем просто `model(imgs)`.
---
## 4. Кластеризация в пространстве признаков / на UMAP
**Главная рекомендация авторов** ([umap-learn → Clustering](https://umap-learn.readthedocs.io/en/latest/clustering.html)): кластеризовать НЕ на 2D-картинке, а на **промежуточном вложении** (например, 10 компонент), либо параметрами, заточенными под кластеризацию.
**Параметры под кластеризацию (рекомендация документации):**
| Параметр | Визуализация | Кластеризация |
|---|---|---|
| `n_components` | 2 | 10 (исследовать диапазон) |
| `n_neighbors` | 15 | 30 (больше — меньше шумовых «разрывов») |
| `min_dist` | 0.1 | 0.0 (плотные кластеры) |
**Два алгоритма:**
- **k-means** — требует заранее знать $k$, ищет сферические кластеры одинаковой плотности, относит ВСЕ точки (нет «шума»). Уместен, если число классов известно (а здесь оно известно — это число жестов Naruto Sign).
- **HDBSCAN** — плотностный, не требует $k$, выделяет шум/выбросы, работает с кластерами произвольной формы и разной плотности. Авторы UMAP в туториале демонстрируют связку **UMAP(intermediate) + HDBSCAN**.
```python
import hdbscan
from sklearn.cluster import KMeans
# UMAP -> intermediate (NOT 2D) embedding for clustering
reducer_clu = umap.UMAP(
n_neighbors=30, min_dist=0.0, n_components=10,
metric="cosine", random_state=42,
)
emb_clu = reducer_clu.fit_transform(X) # [N, 10]
# HDBSCAN (no need to set k; label -1 == noise)
hdb = hdbscan.HDBSCAN(min_cluster_size=15, min_samples=5)
labels_hdb = hdb.fit_predict(emb_clu)
# k-means with known number of gesture classes
k = len(np.unique(y))
labels_km = KMeans(n_clusters=k, n_init=10, random_state=42).fit_predict(emb_clu)
```
**Риски кластеризации прямо на 2D:**
1. UMAP-2D не сохраняет плотность полностью — плотностные алгоритмы (HDBSCAN) делают ложные выводы.
2. UMAP может создавать **ложные разрывы** («false tears») внутри кластеров → переоценка числа кластеров.
3. Геометрия 2D искажена сильнее, чем у 1050-мерного вложения; 2D — только для глаз, не для алгоритма.
Авторы прямо предупреждают, что использование UMAP перед кластеризацией «несколько спорно и требует осторожности».
---
## 5. Метрики качества
**Внутренние (без меток) — оценивают саму геометрию кластеров:**
| Метрика | Диапазон | Лучше | Идея |
|---|---|---|---|
| Silhouette | $[-1, 1]$ | выше | $(b-a)/\max(a,b)$: внутрикласт. сплочённость $a$ vs разделимость $b$ |
| Davies-Bouldin | $[0, \infty)$ | ниже | средн. отношение внутрикласт. разброса к межкласт. расстоянию |
**Внешние (с истинными метками) — согласованность кластеров с классами Naruto Sign:**
| Метрика | Диапазон | Adj. for chance | Основа | Источник |
|---|---|---|---|---|
| ARI | $[-0.5, 1]$ | да | подсчёт пар | [Hubert & Arabie 1985] |
| NMI | $[0, 1]$ | **нет** | теория информации | [scikit-learn clustering](https://scikit-learn.org/stable/modules/clustering.html) |
| AMI | $\sim[0, 1]$ | да | теория информации | [Vinh, Epps, Bailey, JMLR 2010](https://jmlr.csail.mit.edu/papers/volume17/15-627/15-627) |
- **ARI** и **AMI** скорректированы на случайность (≈0 для случайной разметки) — предпочтительны как итоговые. **NMI** не скорректирован: при малом числе точек/большом числе кластеров завышается.
- Формула AMI: $\mathrm{AMI} = \dfrac{\mathrm{MI} - \mathbb{E}[\mathrm{MI}]}{\overline{H} - \mathbb{E}[\mathrm{MI}]}$, где $\overline{H}$ — среднее энтропий разбиений.
- Подробно о выборе ARI vs AMI: [Vinh et al. 2010](https://jmlr.csail.mit.edu/papers/volume17/15-627/15-627).
**Trustworthiness** ([sklearn.manifold.trustworthiness](https://scikit-learn.org/stable/modules/generated/sklearn.manifold.trustworthiness.html)) — мера сохранения локальной структуры при снижении размерности: $\in [0,1]$, выше = меньше ложных близких соседей в проекции. Применяется к самому вложению (а не к кластеризации).
```python
from sklearn.metrics import (silhouette_score, davies_bouldin_score,
adjusted_rand_score, normalized_mutual_info_score,
adjusted_mutual_info_score)
from sklearn.manifold import trustworthiness
# internal (geometry); exclude HDBSCAN noise points (label == -1)
mask = labels_hdb != -1
print("silhouette :", silhouette_score(emb_clu[mask], labels_hdb[mask]))
print("davies-bouldin:", davies_bouldin_score(emb_clu[mask], labels_hdb[mask]))
# external (agreement with true Naruto Sign labels)
print("ARI:", adjusted_rand_score(y, labels_km))
print("NMI:", normalized_mutual_info_score(y, labels_km))
print("AMI:", adjusted_mutual_info_score(y, labels_km))
# embedding quality vs original feature space
print("trustworthiness:", trustworthiness(X, emb_2d, n_neighbors=15))
```
---
## 6. Что покажет анализ и какие гипотезы мотивирует
- **Хорошо отделимые классы** — компактные, изолированные «острова» на UMAP; высокие per-class silhouette; кластер HDBSCAN ≈ один класс. Ожидание: классификатор EdgeNeXt уверенно их различает.
- **Путающиеся классы** — перекрывающиеся облака, точки одного класса распределены по нескольким кластерам или один кластер содержит метки 23 классов. Это прямой предиктор **ошибок классификатора**: перекрытие в эмбеддинге → confusion в матрице ошибок.
- **Сверка с confusion matrix:** пары классов с высоким перекрытием в UMAP должны коррелировать с off-diagonal-массой матрицы ошибок. Совпадение валидирует, что причина ошибок — в признаковом представлении, а не в голове-классификаторе.
**Мотивируемые гипотезы (для HPO/обучения EdgeNeXt):**
1. **Целевая аугментация трудных классов** — для перекрывающихся жестов усилить аугментации, разводящие их (ракурс/освещение/фон), чтобы увеличить межклассовую дистанцию.
2. **Class-balanced / hard-class семплирование** — повысить вес/частоту трудноразделимых классов в батче.
3. **Метрик-обучение** — добавить contrastive/triplet/ArcFace-член, явно растягивающий перекрывающиеся пары.
4. **Ревизия разметки** — точки одного класса в чужом кластере могут быть mislabeled / out-of-distribution кадрами.
> Важно: UMAP здесь — **инструмент генерации гипотез**, а не доказательство. Любой вывод о разделимости подтверждается метриками (§5) и confusion matrix.
---
## 7. Предостережения
1. **Не интерпретировать абсолютные расстояния.** Расстояние между двумя кластерами на UMAP-картинке НЕ отражает их реальную близость в исходном пространстве. Глобальные дистанции искажены ([umap-learn parameters](https://umap-learn.readthedocs.io/en/latest/parameters.html), [pair-code](https://pair-code.github.io/understanding-umap/)).
2. **Не интерпретировать размеры/плотность кластеров.** UMAP, как и t-SNE, не сохраняет плотность полностью; размер «облака» не равен дисперсии класса.
3. **Стохастичность — фиксировать seed.** Без `random_state` форма меняется от запуска к запуску. Фиксируйте seed и в идеале проверяйте устойчивость выводов на 23 разных seed.
4. **Параметры формируют картину.** Сильное изменение `n_neighbors`/`min_dist` радикально меняет вид; нельзя подбирать параметры под желаемый результат. Показывайте использованные значения.
5. **Ложные разрывы.** UMAP может «разорвать» один класс на несколько сгустков — это артефакт, не subclass. Проверяйте на промежуточном вложении и метриками.
6. **Не «доказывать» кластерами то, чего нет.** Наличие визуальных сгустков ≠ наличие классов. Кластеризация на 2D особенно подвержена артефактам — кластеризуйте на 1050-мерном вложении (§4) и валидируйте ARI/AMI/silhouette.
7. **UMAP перед кластеризацией — спорно.** Сами авторы советуют делать это «с осторожностью» и сравнивать с кластеризацией в исходном (или PCA-сжатом) пространстве как sanity-check.
---
### Источники
- [McInnes, Healy, Melville. UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction. arXiv:1802.03426 (2018)](https://arxiv.org/abs/1802.03426)
- [umap-learn — Basic Parameters](https://umap-learn.readthedocs.io/en/latest/parameters.html)
- [umap-learn — Using UMAP for Clustering](https://umap-learn.readthedocs.io/en/latest/clustering.html)
- [Kobak & Linderman. Initialization is critical for preserving global data structure. Nature Biotechnology (2021)](https://www.nature.com/articles/s41587-020-00809-z)
- [pair-code — Understanding UMAP](https://pair-code.github.io/understanding-umap/)
- [Vinh, Epps, Bailey. Information Theoretic Measures for Clusterings Comparison. JMLR (2010)](https://jmlr.csail.mit.edu/papers/volume17/15-627/15-627)
- [scikit-learn — Clustering metrics](https://scikit-learn.org/stable/modules/clustering.html)
- [scikit-learn — trustworthiness](https://scikit-learn.org/stable/modules/generated/sklearn.manifold.trustworthiness.html)