Вопрос или проблема
Я использую AgglomerativeClustering из scikit-learn на большом наборе данных.
Я хотел бы изменить distance_threshold
после того, как модель уже была вычислена. Вычисление модели выполняется медленно (квадратичное время), но переобчисление меток для нового distance_threshold
должно выполняться легко за линейное время, так как модель сохраняет массивы children_
и distances_
постоянно. Но как можно переобчислить метки для другого distance_threshold
?
Можно предположить, что изначально distance_threshold
был установлен на 0, то есть всё дерево было вычислено.
Один из вариантов ускорения вычислений с различными порогами – это кэширование результатов с помощью опции memory
.
Что-то вроде этого:
from sklearn.cluster import AgglomerativeClustering
clustering = AgglomerativeClustering(compute_full_tree=True,
distance_threshold=1.0,
memory='mycachedir',
n_clusters=None,)
Вы можете вычислить новую метку, используя children_
и distances_
рекурсивно, следуя приведенному ниже определению из документации scikit-learn.
children_array-like формы (n_samples-1, 2)
Дети каждого узла, не являющегося листом. Значения меньше n_samples соответствуют листьям дерева, которые являются оригинальными образцами. Узел i, который больше или равен n_samples, является нелистовым узлом и имеет детей children_[i – n_samples]. В альтернативном варианте на i-й итерации, children[i][0] и children[i][1] объединяются, чтобы сформировать узел n_samples + I.
distances_array-like формы (n_nodes-1,)
Расстояния между узлами в соответствующем месте в children_. Вычисляется только если используется distance_threshold или compute_distances установлено в True.
from sklearn.cluster import AgglomerativeClustering
from sklearn import datasets
iris = datasets.load_iris()
x, y = iris.data, iris.target
ac = AgglomerativeClustering(n_clusters=None, affinity='euclidean', linkage="complete", compute_full_tree=True, distance_threshold=0, )
labels = ac.fit_predict(x)
children = ac.children_
distances = ac.distances_
thres = 1.5
n_samples = 150
def get_branch(label): # Возвращает индекс "детей" на основе узла
for idx,child in enumerate(children):
if label in child:
return idx
def new_label(label):
branch = get_branch(label)
distance = distances[branch]
while distance < thres:
parent = branch + n_samples # Согласно документации, это узел родителя
branch = get_branch(parent) # Получаем индекс родителя
distance = distances[branch] # Получаем расстояние элементов в индексе
return label if not parent else parent
new_label(100)
Ответ или решение
Чтобы переоценить метки после выполнения агломеративной кластеризации с использованием scikit-learn
, вам потребуется внести изменения в значение параметра distance_threshold
, не проводя повторную полную кластеризацию, так как это может быть вычислительно затратным для больших наборов данных. Сначала важно отметить, что при исходной настройке distance_threshold=0
создается полное дерево, и вы можете сохранить информацию о структуре дерева в массивы children_
и distances_
.
Вот шаги, которые вам нужно выполнить для переоценки меток при изменении distance_threshold
:
1. Использование compute_full_tree
Прежде всего, убедитесь, что вы создаете модель с параметрами n_clusters=None
и compute_full_tree=True
. Это позволит вам сохранить полное дерево кластеризации:
from sklearn.cluster import AgglomerativeClustering
from sklearn import datasets
# Загружаем данные
iris = datasets.load_iris()
X = iris.data
# Создаем агломеративную кластеризацию
ac = AgglomerativeClustering(n_clusters=None, compute_full_tree=True, distance_threshold=0)
labels = ac.fit_predict(X)
2. Изменение порога расстояния
После того как у вас уже есть работающая модель, вы можете пересчитать метки для нового значения distance_threshold
. Для этого вам нужно будет рекурсивно пройтись по массиву children_
и использовать информацию из distances_
.
3. Реализация функции для переоценки меток
import numpy as np
# Дерево слияний
children = ac.children_
distances = ac.distances_
n_samples = X.shape[0] # Общее количество точек данных
def get_branch(label):
for idx, child in enumerate(children):
if label in child:
return idx
return None
def new_label(label, threshold):
branch = get_branch(label)
distance = distances[branch]
while distance < threshold:
# Рассмотрим родительский узел
parent = branch + n_samples
branch = get_branch(parent)
if branch is None: # Если родительский узел не существует, выходим
break
distance = distances[branch]
return label if branch is None else parent
# Пример использования с новым значением порога
threshold = 1.5
new_label_result = new_label(100, threshold) # Замените 100 на индекс узла, который хотите перетасовать
print(f'Новая метка для узла 100 при пороге {threshold}: {new_label_result}')
4. Пояснение функции
- Функция
get_branch
находит индекс узла, базируясь на переданном значенииlabel
. - Функция
new_label
будет перебирать узлы дерева, пока их расстояние меньше заданного порогаthreshold
. Если расстояние превышает порог, остановите рекурсию и верните новую метку.
Заключение
Следуя вышеописанным шагам, вы сможете эффективно пересчитывать метки агломеративной кластеризации при изменении значения distance_threshold
, избегая повторного выполнения полной кластеризации и существенно сокращая время обработки для больших наборов данных.