1.Проклятие размерности

Человек эволюционировал в 3 пространственных измерениях, и в них мы себя шикарно чувствуем. В них мы живем, радуемся, грустим, да и все драмы жизни проходят в этих измерениях. Правда в первой половине 20 века Теодор Калуца и Оскар Клейн нашли еще одно измерение, но оно маленькое и его людям не видно. После струнные теоретики, такие как Леонард Сасскинд, Герард т`Хофт, Яу Шинтун, Александр Виленкин  и другие, опять сильно усложнили картину мира, и к 4 пространственным измерениям добавили еще 6 (это минимум), но они все где то не пойми где, и влияют на жизнь только физиков-теоретиков, а остальным n-миллиардам людей на Земле, нет никакого дела до этих измерений, им и в 3 хорошо живется.

Другое дело математика и наука о данных, тут измерений может быть сколько угодно, например вот:

df = pd.DataFrame({
    "age": np.random.randint(18, 60, n_samples),   # возраст
    "height": np.random.randint(150, 200, n_samples),  # рост
    "weight": np.random.randint(50, 100, n_samples),   # вес
    "gender": np.random.choice(["male", "female"], n_samples),  # категориальный
    "city": np.random.choice(["Berlin", "Paris", "London", "Rome"], n_samples),  # категориальный
    "education": np.random.choice(["highschool", "bachelor", "master", "phd"], n_samples),
    "occupation": np.random.choice(["engineer", "teacher", "doctor", "artist"], n_samples),
    "hobby": np.random.choice(["sports", "music", "gaming", "reading"], n_samples),
    "married": np.random.choice(["yes", "no"], n_samples),
    "children": np.random.randint(0, 4, n_samples)  # числовой
})

DataFrame сгенерирвоан ChatGPT

Это синтетический датафрейм, и в нём 10 измерений: каждый признак — такой как age, height и т. д. — представляет собой отдельное измерение. Но здесь и начинаются проблемы. В реальных проектах число измерений может достигать сотен или даже тысяч, и человеку в силу природы трудно осмыслить и упорядочить такую информацию. Кроме того, возникает серьёзная проблема с визуализацией: на привычном двумерном графике данные оказываются «размазанными» и не дают понимания, какие классы близки друг к другу.

Прежде чем продолжить, нам надо разобрать одну важную часть — евклидово расстояние, то есть кратчайший путь по прямой между двумя точками. Почему это важно? В привычной декартовой системе координат (график с осями X и Y) именно эта метрика даёт естественное представление о том, какие объекты ближе друг к другу. Другие расстояния, такие как манхэттенское или Чебышева, применимы в других задачах, но для визуализации они мало помогают.

Формально евклидово расстояние вычисляется как корень из суммы квадратов разностей по каждому измерению. Но в высоких размерностях даже небольшие различия по многим координатам накапливаются, и все расстояния начинают выравниваться. В итоге ближайший сосед оказывается почти так же далёк, как и самый удалённый объект. Данные «расползаются» по углам многомерного пространства, и на визуализации мы видим лишь равномерное облако точек без структуры.

Давайте запишем формулу евклидова расстояния, чтобы лучше понять его. Вот у нас есть таблица

age

height

weight

25

180

75

30

175

70

Тогда:

(x_1, x_2, x_3) = (25, 180, 75), \quad (y_1, y_2, y_3) = (30, 175, 70)d(x, y) = √((25-30)**2 + (180- 175)**2+(75 -70)**2)  = √75 ≈8.66

Как видно, вычисление несложно, особенно если у нас всего две строки данных. Но уже при десятках признаков и тысячах объектов лучше использовать готовые библиотеки, чтобы избежать ошибок и сэкономить время.

Возвращаемся к визуализации. Если попробовать построить scatter-график для датафрейма из начала статьи, то получится следующее:

Если попробовать визуализировать датафрейм из начала статьи с помощью метода scatter библиотеки matplotlib, то получится следующее.

Глядя на график, мы не можем понять, какие классы действительно соседствуют, а какие разделены.

2.Как избавится от проклятия

Большая размерность данных — это не приговор. Да, существуют классические методы снижения размерности, такие как PCA и LDA. О них написано множество книг и статей, поэтому я не буду повторяться. Здесь я хочу рассказать о другом подходе — методе, который не сводит данные к линейным комбинациям признаков, а помогает увидеть их структуру в привычных двух или трёх измерениях. Этот метод называется t-SNE, и о нём, по крайней мере мне, встречалось гораздо меньше материалов.

t-SNE отлично работает со сложными наборами данных. Он позволяет визуализировать их в 2D или 3D так, чтобы сохранить локальную структуру. Интуитивно принцип работы таков: алгоритм оценивает расстояния между точками в исходном многомерном пространстве и на их основе вычисляет вероятность того, что объекты являются соседями. Затем он подбирает такое расположение точек в 2D/3D, чтобы эти вероятности как можно точнее сохранялись.

Упрощённо процесс можно описать так:

1.     Вычисляем вероятность того, что в исходном пространстве точки являются соседями.

2.     Сжимаем пространство в 2D или 3D. В новом пространстве близость точек моделируется через t-распределение Стьюдента с одной степенью свободы.

3.     Минимизируем расхождение: стремимся, чтобы соседи в исходном пространстве оставались соседями и в новом. Для этого используется дивергенция Кульбака–Лейблера.

4.     Оптимизируем: применяем стохастический градиентный спуск, чтобы найти такое расположение точек, при котором функция ошибки минимальна.

В этом объяснении я сознательно не использовал формулы, потому что цель статьи — не математический разбор t-SNE, а знакомство с удобным способом визуализации данных. В реальной жизни вряд ли кому-то придётся писать этот алгоритм с нуля, зато с библиотекой scikit-learn работать с ним не работа а сплошное удовольствие . Ниже я покажу пример с её помощью, используя встроенный датасет load_digits, который применяется для задач классификации рукописных цифр.

import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.preprocessing import StandardScaler
from sklearn.manifold import TSNE

# 2. Загрузка датасета (цифры 0–9, по 64 признака — 8x8 изображение)
data = load_digits()
X = data.data        # признаки
y = data.target      # метки классов

# 3. (Рекомендуется) Стандартизация признаков
scaler = StandardScaler()
X_std = scaler.fit_transform(X)

# 4. Применение t-SNE
tsne = TSNE(n_components=2, perplexity=30, max_iter=500, random_state=42)
X_tsne = tsne.fit_transform(X_std)

# 5. Визуализация
plt.figure(figsize=(10, 6))
scatter = plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='tab10', s=20, alpha=0.8)
plt.colorbar(scatter, label="Класс (цифра)")
plt.title('t-SNE проекция данных (Digits Dataset)')
plt.xlabel('t-SNE 1')
plt.ylabel('t-SNE 2')
plt.grid(True)
plt.tight_layout()
plt.show()

t-SNE проекция данных: алгоритм свёл 64 признака изображений цифр в двумерное пространство. Видно 10 отчётливых кластеров, соответствующих цифрам 0–9.

Теперь давайте поговорим о гиперпараметрах t-SNE:

1.     n_components– размерность нового пространства. Обычно используют 2 (для плоской картинки) или 3 (для трёхмерной визуализации). Больше почти никогда не имеет смысла.

2.     perplexity – число «эффективных соседей». Малое значение (5–10) акцентирует локальные связи, в итоге получается много мелких кластеров. Большое значение (40–50) даёт более глобальную картину, кластеры могут сливаться. Обычно используют диапазон 5–50.

3.     max_iter(n_iter – зависит от версии библиотеки) – количество итераций градиентного спуска. Малое значение - результат сырой и неустойчивый. Большое - качественнее, но медленнее. Обычно 500–1000 достаточно.

4.     random_state – Стандартный гиперпараметр scikit-learn, используется для того, чтобы результат был одинаковым при нескольких запусках кода .

5.     learning_rate – скорость обучения. Если слишком маленькая, алгоритм сходится медленно и может «слипнуть» точки. Если слишком большая, точки «разлетаются хаотично». Обычно выбирают в диапазоне 10–1000, по умолчанию auto.

6.     metric – метрика расстояния в исходном пространстве. По умолчанию используется евклидова, но можно пробовать и другие (косинусная, манхэттенская и т. д.).

7.     early_exaggeration – параметр, который искусственно увеличивает различия между точками на первых итерациях, помогая кластерам разойтись. Обычно по умолчанию 12.0.

3. Да это же обучение без учителя!?

И да, и нет. Формально t-SNE относится к методам обучения без учителя, потому что он работает без заранее заданных меток классов. Алгоритм действительно «ничего не знает» о содержании данных: ему всё равно, цифры это, молекулы или тексты. Он видит только расстояния между точками и старается сохранить их относительное положение. Проще говоря, t-SNE строит топологическую карту данных — показывает форму рельефа: где «возвышенности» (плотные кластеры), а где «долины» (редкие области).

Но важно понимать отличие от алгоритмов кластеризации, таких как k-means или DBSCAN.

t-SNE лишь показывает геометрию — кто с кем рядом, но не говорит: «это кластер номер 1, это кластер номер 2». Кластеризация идёт дальше: она старается разделить пространство и присвоить каждой точке ярлык (пусть и условный).

Таким образом, t-SNE — это инструмент для визуального анализа, своего рода карта рельефа многомерного пространства. Он не решает задачу классификации, но помогает человеку «увидеть» структуру данных. А уже поверх этой карты можно применять алгоритмы кластеризации или обучения с учителем, если нужно формальное разделение.

4. Будущее визуализации данных

Недавно я задумался о том, что геометрия и, в большей степени, топология как раз и занимаются тем, что знакомят человека с тем, как выглядит пространство. К примеру, потоки Риччи — это способ эволюции метрики, который позволяет «разгладить» поверхность и понять её структуру. В XX веке этот подход применили Калаби и Яу, чтобы описать особые многомерные объекты, известные как, собственно, пространства Калаби–Яу.

Любопытно, что сегодня потоки Риччи уже начали применять в экспериментах в Data Science. И это наводит на мысль: многие методы из теоретических областей математики, таких как геометрия и топология, вполне могут найти своё место в машинном обучении. Мы пока используем только часть возможных инструментов, но горизонты здесь огромные.

Комментарии (0)