Случайные разрезы данных в задаче кластеризации: коротко

от автора

Кластеризация — штука сложная. Вроде все просто: сгруппировать похожее с похожим. Но когда данных вагон, а структура запутаннее клубка проводов за столом, стандартные методы вроде k‑means или DBSCAN начинают сдавать позиции. Особенно больно, если данные нелинейные, а пространство хоть на глобус натягивай.

И вот тут хорошо зайдет Random Cuts — подход, который берет все это хаосное пространство и начинает резать его случайными гиперплоскостями.

Когда мы создаем гиперплоскость, задавая ее нормаль и смещение, мы фактически вводим линейное неравенство, разделяющее пространство. Если точка находится в области с высокой плотностью (то есть много точек сосредоточено рядом), ее изоляция потребует больше разрезов, так как каждый разрез уменьшает количество соседей. Чем больше разрезов, тем «глубже» точка в структуре данных.

С другой стороны, если точка изначально находится в области с низкой плотностью (например, на периферии или в «аномальной» зоне), то уже первый или второй разрез отделяет ее. Таким образом, Random Cuts позволяют строить оценку плотности, используя концепцию глубины в дереве разрезов.

Переходим к делу

Посмотрим, как это всё работает в коде. Будем использовать Python и установим нужные библиотеки:

pip install numpy scikit-learn matplotlib

Начнем с того, чтобы понять, как сгенерировать случайную гиперплоскость. Пусть у нас есть -мерное пространство.

import numpy as np  def generate_random_hyperplane(dimensions):     # Генерация случайного вектора нормали     normal_vector = np.random.normal(size=dimensions)     normal_vector /= np.linalg.norm(normal_vector)  # Нормализация     # Генерация смещения     offset = np.random.uniform(-1, 1)     return normal_vector, offset  # Пример для 2D пространства normal, offset = generate_random_hyperplane(2) print(f"Нормаль: {normal}, Смещение: {offset}")

Теперь попробуем разделить данные. Мы сгенерируем случайные точки и посмотрим, как они делятся относительно гиперплоскости.

def split_data(points, normal, offset):     # Вычисляем скалярное произведение для каждой точки     distances = np.dot(points, normal) - offset     # Разделяем данные по знаку расстояния     left = points[distances < 0]     right = points[distances >= 0]     return left, right  # Генерация случайных данных data = np.random.uniform(-5, 5, (100, 2))  # Деление данных left, right = split_data(data, normal, offset)  # Визуализация import matplotlib.pyplot as plt  plt.scatter(left[:, 0], left[:, 1], c='blue', label='Left') plt.scatter(right[:, 0], right[:, 1], c='red', label='Right') plt.axline((0, offset), slope=-normal[0]/normal[1], color='green', label='Hyperplane') plt.legend() plt.show()

И получаем график:

График 1

График 1

На этом графике можем увидеть процесс разделения данных случайной гиперплоскостью.

  • Синие точки представляют одну часть данных, лежащую по одну сторону гиперплоскости.

  • Красные точки лежат по другую сторону гиперплоскости.

  • Зелёная линия — это сама гиперплоскость, разделяющая данные. Ее наклон определяется случайным вектором нормали, а смещение — случайным значением в заданных пределах.

Переходим к самому интересному. Теперь создадим лес случайных разрезов и попробуем кластеризовать данные. Здесь потребуется чуть больше кода.

class RandomCutTree:     def __init__(self, depth=0):         self.left = None         self.right = None         self.normal = None         self.offset = None         self.depth = depth      def fit(self, points):         if len(points) <= 1 or self.depth > 10:  # Ограничение по глубине             return         # Генерируем случайную гиперплоскость         dimensions = points.shape[1]         self.normal, self.offset = generate_random_hyperplane(dimensions)         # Разделяем данные         left_points, right_points = split_data(points, self.normal, self.offset)         # Рекурсивное построение дерева         self.left = RandomCutTree(self.depth + 1)         self.right = RandomCutTree(self.depth + 1)         self.left.fit(left_points)         self.right.fit(right_points)      def predict(self, point):         if self.left is None and self.right is None:             return self.depth  # Возвращаем глубину как "оценку"         distance = np.dot(point, self.normal) - self.offset         if distance < 0:             return self.left.predict(point)         else:             return self.right.predict(point)  # Пример работы tree = RandomCutTree() tree.fit(data)  # Оценка точек scores = [tree.predict(point) for point in data]  # Визуализация plt.scatter(data[:, 0], data[:, 1], c=scores, cmap='viridis') plt.colorbar(label='Depth Score') plt.show()

В итоге получим график:

График 2

График 2

На втором графике можно увидеть распределение «глубины» точек в дереве случайных разрезов. Глубина точки показывает, насколько далеко в иерархии дерева она оказалась, то есть сколько раз данные пришлось разрезать, чтобы изолировать эту точку. Светлые точки ближе к корню — они изолируются быстро, что может намекать на их одиночество или аномальность. Темные точки глубже в дереве — они группируются с другими и требуют больше разрезов, чтобы их отделить, что уже похоже на плотные кластеры.

Если на графике заметны явно выделенные области со схожей глубиной, это сигнал о кластеризации данных: дерево случайных разрезов «подсказало» нам, где данные тесно связаны. А вот точки, глубина которых резко отличается от соседних, могут быть признаками аномалий.


Random Cuts — хороший инструмент для работы с нелинейными, сложными и высокоразмерными данными. Он позволяет:

  1. Выявлять аномалии: быстро изолируемые точки (малой глубины) часто указывают на одиночные, необычные значения.

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

  3. Анализировать структуры данных: даже без явного предположения о форме распределения Random Cuts дают неплохую эвристику, полезную для предварительного анализа.

Метод легко адаптируется под разные задачи, работает с любыми размерами данных и предоставляет результат, который интуитивно интерпретируется.

17 декабря в Otus пройдет открытый урок «Абстракция как математический объект».

Речь пойдет о том, что такое абстракция и почему она важна в программировании. Вы узнаете, как правильно выбранные абстракции помогают упростить сложные системы и сделать код более понятным. Записаться можно по ссылке.


ссылка на оригинал статьи https://habr.com/ru/articles/863528/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *